Initial Contribution

This commit is contained in:
The Android Open Source Project
2008-10-21 07:00:00 -07:00
commit 5c11852110
2143 changed files with 253519 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
<?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="output" path="bin"/>
</classpath>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>adt</name>
<comment></comment>
<projects>
</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,36 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Android Development Toolkit
Bundle-SymbolicName: com.android.ide.eclipse.adt;singleton:=true
Bundle-Version: 0.8.1.qualifier
Bundle-ClassPath: .,
jarutils.jar,
androidprefs.jar
Bundle-Activator: com.android.ide.eclipse.adt.AdtPlugin
Bundle-Vendor: The Android Open Source Project
Require-Bundle: com.android.ide.eclipse.common,
com.android.ide.eclipse.ddms,
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
Eclipse-LazyStart: true
Export-Package: com.android.ide.eclipse.adt.build;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.adt.project;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.adt.project.internal;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.adt.resources;x-friends:="com.android.ide.eclipse.tests"

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,10 @@
bin.includes = plugin.xml,\
META-INF/,\
icons/,\
.,\
templates/,\
about.ini,\
jarutils.jar,\
androidprefs.jar
source.. = src/
output.. = bin/

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: 308 B

View File

@@ -0,0 +1,308 @@
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
<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.project.internal.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"/>
</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.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 Application..."
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"/>
</extension>
<extension
point="org.eclipse.core.runtime.preferences">
<initializer class="com.android.ide.eclipse.adt.preferences.PreferenceInitializer"/>
</extension>
<extension
point="org.eclipse.ui.editors">
<editor
class="com.android.ide.eclipse.adt.editors.java.ReadOnlyJavaEditor"
contributorClass="org.eclipse.ui.texteditor.BasicTextEditorActionContributor"
default="true"
filenames="R.java, Manifest.java"
icon="icons/android.png"
id="com.android.ide.eclipse.adt.editors.java.ReadOnlyJavaEditor"
name="Android Java Editor"/>
</extension>
<extension
id="com.android.ide.eclipse.adt.adtProblem"
name="Generic ADT Problem"
point="org.eclipse.core.resources.markers">
<super type="org.eclipse.core.resources.problemmarker"/>
<persistent value="true"/>
</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.adt.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>
</plugin>

View File

@@ -0,0 +1,51 @@
/*
* 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;
/**
* 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$
/** 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,180 @@
/*
* 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.ddmlib.Device;
import com.android.ide.eclipse.adt.AdtPlugin.CheckSdkErrorHandler;
import com.android.ide.eclipse.common.AndroidConstants;
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 SDK build incremental version from the
* <code>$SDK/tools/lib/build.prop file</code>. */
private final static Pattern sBuildVersionPattern = Pattern.compile(
"^" + Device.PROP_BUILD_VERSION + "=(.+)$"); //$NON-NLS-1$
/**
* Pattern to parse release type SDK version number. This parses the content read with
* <code>sBuildIdPattern</code>.
*/
private final static Pattern sSdkVersionPattern = Pattern.compile(
"^(\\d+)\\.(\\d+)_r(\\d+)$", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
/**
* Pattern to get the minimum plugin version supported by the SDK. This is read from
* the file <code>$SDK/tools/lib/plugin.prop</code>.
*/
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 + AndroidConstants.OS_SDK_LIBS_FOLDER;
/*
* All plugins should work with all SDKs. Newer SDKs may require a newer plugin
* but this is handled below.
* Still, we need to grab the SDK version from this file. This is used
* to compare to running emulator/device when launching run/debug sessions.
*/
try {
FileReader reader = new FileReader(osLibs + AndroidConstants.FN_BUILD_PROP);
BufferedReader bReader = new BufferedReader(reader);
String line;
while ((line = bReader.readLine()) != null) {
Matcher m = sBuildVersionPattern.matcher(line);
if (m.matches()) {
plugin.mSdkApiVersion = m.group(1).trim();
/*
* No checks on the version at the moment.
*/
/*
if (plugin.mSdkBuildVersion != null) {
// attempt to get version number from the build id
m = sSdkVersionPattern.matcher(plugin.mSdkBuildVersion);
if (m.matches()) {
// get the platform version number
int platformMajor = Integer.parseInt(m.group(1));
int platformMinor = Integer.parseInt(m.group(2));
@SuppressWarnings("unused") //$NON-NLS-1$
int sdkRelease = Integer.parseInt(m.group(3));
if (platformMajor != 0 || platformMinor != 9) {
return errorHandler.handleError(String.format(
"This version of ADT requires the Android SDK version 0.9\n\nCurrent version is %1$s.\n\nPlease update your SDK to the latest version.",
plugin.mSdkBuildVersion));
}
} else {
// unknown version format.
AdtPlugin.printErrorToConsole(
(Object)String.format(Messages.VersionCheck_Unable_To_Parse_Version_s,
plugin.mSdkBuildVersion));
}
}
*/
break;
}
}
} catch (FileNotFoundException e) {
// the build id will be null, and this is handled by the builders.
} catch (IOException e) {
// the build id will be null, and this is handled by the builders.
}
// 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 + AndroidConstants.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,980 @@
/*
* 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.common.AndroidConstants;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
import com.android.jarutils.DebugKeyProvider;
import com.android.jarutils.JavaResourceFilter;
import com.android.jarutils.SignedJarBuilder;
import com.android.jarutils.DebugKeyProvider.IKeyGenOutput;
import com.android.jarutils.DebugKeyProvider.KeytoolException;
import com.android.jarutils.SignedJarBuilder.IZipEntryFilter;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.preference.IPreferenceStore;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;
public class ApkBuilder extends BaseBuilder {
public static final String ID = "com.android.ide.eclipse.adt.ApkBuilder"; //$NON-NLS-1$
private static final String PROPERTY_CONVERT_TO_DEX = "convertToDex"; //$NON-NLS-1$
private static final String PROPERTY_PACKAGE_RESOURCES = "packageResources"; //$NON-NLS-1$
private static final String PROPERTY_BUILD_APK = "buildApk"; //$NON-NLS-1$
private static final String DX_PREFIX = "Dx"; //$NON-NLS-1$
/**
* Dex conversion flag. This is set to true if one of the changed/added/removed
* file is a .class file. Upon visiting all the delta resource, if this
* flag is true, then we know we'll have to make the "classes.dex" file.
*/
private boolean mConvertToDex = false;
/**
* Package resources flag. This is set to true if one of the changed/added/removed
* file is a resource file. Upon visiting all the delta resource, if
* this flag is true, then we know we'll have to repackage the resources.
*/
private boolean mPackageResources = false;
/**
* Final package build flag.
*/
private boolean mBuildFinalPackage = false;
private PrintStream mOutStream = null;
private PrintStream mErrStream = null;
/**
* Basic Resource Delta Visitor class to check if a referenced project had a change in its
* compiled java files.
*/
private static class ReferencedProjectDeltaVisitor implements IResourceDeltaVisitor {
private boolean mConvertToDex = false;
private boolean mMakeFinalPackage;
private IPath mOutputFolder;
private ArrayList<IPath> mSourceFolders;
private ReferencedProjectDeltaVisitor(IJavaProject javaProject) {
try {
mOutputFolder = javaProject.getOutputLocation();
mSourceFolders = BaseProjectHelper.getSourceClasspaths(javaProject);
} catch (JavaModelException e) {
} finally {
}
}
public boolean visit(IResourceDelta delta) throws CoreException {
// no need to keep looking if we already know we need to convert
// to dex and make the final package.
if (mConvertToDex && mMakeFinalPackage) {
return false;
}
// get the resource and the path segments.
IResource resource = delta.getResource();
IPath resourceFullPath = resource.getFullPath();
if (mOutputFolder.isPrefixOf(resourceFullPath)) {
int type = resource.getType();
if (type == IResource.FILE) {
String ext = resource.getFileExtension();
if (AndroidConstants.EXT_CLASS.equals(ext)) {
mConvertToDex = true;
}
}
return true;
} else {
for (IPath sourceFullPath : mSourceFolders) {
if (sourceFullPath.isPrefixOf(resourceFullPath)) {
int type = resource.getType();
if (type == IResource.FILE) {
// check if the file is a valid file that would be
// included during the final packaging.
if (checkFileForPackaging((IFile)resource)) {
mMakeFinalPackage = true;
}
return false;
} else if (type == IResource.FOLDER) {
// if this is a folder, we check if this is a valid folder as well.
// If this is a folder that needs to be ignored, we must return false,
// so that we ignore its content.
return checkFolderForPackaging((IFolder)resource);
}
}
}
}
return true;
}
/**
* Returns if one of the .class file was modified.
*/
boolean needDexConvertion() {
return mConvertToDex;
}
boolean needMakeFinalPackage() {
return mMakeFinalPackage;
}
}
/**
* {@link IZipEntryFilter} to filter out everything that is not a standard java resources.
* <p/>Used in {@link SignedJarBuilder#writeZip(java.io.InputStream, IZipEntryFilter)} when
* we only want the java resources from external jars.
*/
private final IZipEntryFilter mJavaResourcesFilter = new JavaResourceFilter();
public ApkBuilder() {
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 a project object
IProject project = getProject();
// get the list of referenced projects.
IProject[] referencedProjects = ProjectHelper.getReferencedProjects(project);
IJavaProject[] referencedJavaProjects = getJavaProjects(referencedProjects);
// get the output folder, this method returns the path with a trailing
// separator
IJavaProject javaProject = JavaCore.create(project);
IFolder outputFolder = BaseProjectHelper.getOutputFolder(project);
// now we need to get the classpath list
ArrayList<IPath> sourceList = BaseProjectHelper.getSourceClasspaths(javaProject);
// First thing we do is go through the resource delta to not
// lose it if we have to abort the build for any reason.
if (kind == FULL_BUILD) {
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
Messages.Start_Full_Apk_Build);
mPackageResources = true;
mConvertToDex = true;
mBuildFinalPackage = true;
} else {
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
Messages.Start_Inc_Apk_Build);
// go through the resources and see if something changed.
IResourceDelta delta = getDelta(project);
if (delta == null) {
mPackageResources = true;
mConvertToDex = true;
mBuildFinalPackage = true;
} else {
ApkDeltaVisitor dv = new ApkDeltaVisitor(this, sourceList, outputFolder);
delta.accept(dv);
// save the state
mPackageResources |= dv.getPackageResources();
mConvertToDex |= dv.getConvertToDex();
mBuildFinalPackage |= dv.getMakeFinalPackage();
if (dv.mXmlError) {
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
Messages.Xml_Error);
// if there was some XML errors, we just return w/o doing
// anything since we've put some markers in the files anyway
return referencedProjects;
}
}
// also go through the delta for all the referenced projects, until we are forced to
// compile anyway
for (int i = 0 ; i < referencedJavaProjects.length &&
(mBuildFinalPackage == false || mConvertToDex == false); i++) {
IJavaProject referencedJavaProject = referencedJavaProjects[i];
delta = getDelta(referencedJavaProject.getProject());
if (delta != null) {
ReferencedProjectDeltaVisitor dv = new ReferencedProjectDeltaVisitor(
referencedJavaProject);
delta.accept(dv);
// save the state
mConvertToDex |= dv.needDexConvertion();
mBuildFinalPackage |= dv.needMakeFinalPackage();
}
}
}
// do some extra check, in case the output files are not present. This
// will force to recreate them.
IResource tmp = null;
if (mPackageResources == false && outputFolder != null) {
tmp = outputFolder.findMember(AndroidConstants.FN_RESOURCES_AP_);
if (tmp == null || tmp.exists() == false) {
mPackageResources = true;
mBuildFinalPackage = true;
}
}
if (mConvertToDex == false && outputFolder != null) {
tmp = outputFolder.findMember(AndroidConstants.FN_CLASSES_DEX);
if (tmp == null || tmp.exists() == false) {
mConvertToDex = true;
mBuildFinalPackage = true;
}
}
// also check the final file!
String finalPackageName = project.getName() + AndroidConstants.DOT_ANDROID_PACKAGE;
if (mBuildFinalPackage == false && outputFolder != null) {
tmp = outputFolder.findMember(finalPackageName);
if (tmp == null || (tmp instanceof IFile &&
tmp.exists() == false)) {
String msg = String.format(Messages.s_Missing_Repackaging, finalPackageName);
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg);
mBuildFinalPackage = true;
}
}
// store the build status in the persistent storage
saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX , mConvertToDex);
saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);
// Now check the compiler compliance level, not displaying the error
// message since this is not the first builder.
if (ProjectHelper.checkCompilerCompliance(getProject())
!= ProjectHelper.COMPILER_COMPLIANCE_OK) {
return referencedProjects;
}
// now check if the project has problem marker already
if (ProjectHelper.hasError(project, true)) {
// we found a marker with error severity: we abort the build.
// Since this is going to happen every time we save a file while
// errors are remaining, we do not force the display of the console, which
// would, in most cases, show on top of the Problem view (which is more
// important in that case).
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
Messages.Project_Has_Errors);
return referencedProjects;
}
if (outputFolder == null) {
// mark project and exit
markProject(AdtConstants.MARKER_ADT, Messages.Failed_To_Get_Output,
IMarker.SEVERITY_ERROR);
return referencedProjects;
}
// first thing we do is check that the SDK directory has been setup.
String osSdkFolder = AdtPlugin.getOsSdkFolder();
if (osSdkFolder.length() == 0) {
// this has already been checked in the precompiler. Therefore,
// while we do have to cancel the build, we don't have to return
// any error or throw anything.
return referencedProjects;
}
// at this point we know if we need to recreate the temporary apk
// or the dex file, but we don't know if we simply need to recreate them
// because they are missing
// refresh the output directory first
IContainer ic = outputFolder.getParent();
if (ic != null) {
ic.refreshLocal(IResource.DEPTH_ONE, monitor);
}
// we need to test all three, as we may need to make the final package
// but not the intermediary ones.
if (mPackageResources || mConvertToDex || mBuildFinalPackage) {
IPath binLocation = outputFolder.getLocation();
if (binLocation == null) {
markProject(AdtConstants.MARKER_ADT, Messages.Output_Missing,
IMarker.SEVERITY_ERROR);
return referencedProjects;
}
String osBinPath = binLocation.toOSString();
// Remove the old .apk.
// This make sure that if the apk is corrupted, then dx (which would attempt
// to open it), will not fail.
String osFinalPackagePath = osBinPath + File.separator + finalPackageName;
File finalPackage = new File(osFinalPackagePath);
// if delete failed, this is not really a problem, as the final package generation
// handle already present .apk, and if that one failed as well, the user will be
// notified.
finalPackage.delete();
// first we check if we need to package the resources.
if (mPackageResources) {
// need to figure out some path before we can execute aapt;
// resource to the AndroidManifest.xml file
IResource manifestResource = project .findMember(
AndroidConstants.WS_SEP + AndroidConstants.FN_ANDROID_MANIFEST);
if (manifestResource == null
|| manifestResource.exists() == false) {
// mark project and exit
String msg = String.format(Messages.s_File_Missing,
AndroidConstants.FN_ANDROID_MANIFEST);
markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
return referencedProjects;
}
// get the resource folder
IFolder resFolder = project.getFolder(
AndroidConstants.WS_RESOURCES);
// and the assets folder
IFolder assetsFolder = project.getFolder(
AndroidConstants.WS_ASSETS);
// we need to make sure this one exists.
if (assetsFolder.exists() == false) {
assetsFolder = null;
}
IPath resLocation = resFolder.getLocation();
IPath manifestLocation = manifestResource.getLocation();
if (resLocation != null && manifestLocation != null) {
String osResPath = resLocation.toOSString();
String osManifestPath = manifestLocation.toOSString();
String osAssetsPath = null;
if (assetsFolder != null) {
osAssetsPath = assetsFolder.getLocation().toOSString();
}
if (executeAapt(project, osManifestPath, osResPath,
osAssetsPath, osBinPath + File.separator +
AndroidConstants.FN_RESOURCES_AP_) == false) {
// aapt failed. Whatever files that needed to be marked
// have already been marked. We just return.
return referencedProjects;
}
// build has been done. reset the state of the builder
mPackageResources = false;
// and store it
saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
}
}
// then we check if we need to package the .class into classes.dex
if (mConvertToDex) {
if (executeDx(javaProject, osBinPath, osBinPath + File.separator +
AndroidConstants.FN_CLASSES_DEX, referencedJavaProjects) == false) {
// dx failed, we return
return referencedProjects;
}
// build has been done. reset the state of the builder
mConvertToDex = false;
// and store it
saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex);
}
// now we need to make the final package from the intermediary apk
// and classes.dex
if (finalPackage(osBinPath + File.separator + AndroidConstants.FN_RESOURCES_AP_,
osBinPath + File.separator + AndroidConstants.FN_CLASSES_DEX,
osFinalPackagePath, javaProject, referencedJavaProjects) == false) {
return referencedProjects;
} else {
// get the resource to bin
outputFolder.refreshLocal(IResource.DEPTH_ONE, monitor);
// build has been done. reset the state of the builder
mBuildFinalPackage = false;
// and store it
saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(),
"Build Success!");
}
}
return referencedProjects;
}
@Override
protected void startupOnInitialize() {
super.startupOnInitialize();
// load the build status. We pass true as the default value to
// force a recompile in case the property was not found
mConvertToDex = loadProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX , true);
mPackageResources = loadProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, true);
mBuildFinalPackage = loadProjectBooleanProperty(PROPERTY_BUILD_APK, true);
}
/**
* Executes aapt. If any error happen, files or the project will be marked.
* @param project The Project
* @param osManifestPath The path to the manifest file
* @param osResPath The path to the res folder
* @param osAssetsPath The path to the assets folder. This can be null.
* @param osOutFilePath The path to the temporary resource file to create.
* @return true if success, false otherwise.
*/
private boolean executeAapt(IProject project, String osManifestPath,
String osResPath, String osAssetsPath, String osOutFilePath) {
// Create the command line.
ArrayList<String> commandArray = new ArrayList<String>();
commandArray.add(AdtPlugin.getOsAbsoluteAapt());
commandArray.add("package"); //$NON-NLS-1$
commandArray.add("-f");//$NON-NLS-1$
if (AdtPlugin.getBuildVerbosity() == AdtConstants.BUILD_VERBOSE) {
commandArray.add("-v"); //$NON-NLS-1$
}
commandArray.add("-M"); //$NON-NLS-1$
commandArray.add(osManifestPath);
commandArray.add("-S"); //$NON-NLS-1$
commandArray.add(osResPath);
if (osAssetsPath != null) {
commandArray.add("-A"); //$NON-NLS-1$
commandArray.add(osAssetsPath);
}
commandArray.add("-I"); //$NON-NLS-1$
commandArray.add(AdtPlugin.getOsAbsoluteFramework());
commandArray.add("-F"); //$NON-NLS-1$
commandArray.add(osOutFilePath);
String command[] = commandArray.toArray(
new String[commandArray.size()]);
if (AdtPlugin.getBuildVerbosity() == AdtConstants.BUILD_VERBOSE) {
StringBuilder sb = new StringBuilder();
for (String c : command) {
sb.append(c);
sb.append(' ');
}
AdtPlugin.printToConsole(project, sb.toString());
}
// launch
int execError = 1;
try {
// launch the command line process
Process process = Runtime.getRuntime().exec(command);
// list to store each line of stderr
ArrayList<String> results = new ArrayList<String>();
// get the output and return code from the process
execError = grabProcessOutput(process, results);
// attempt to parse the error output
boolean parsingError = parseAaptOutput(results, project);
// if we couldn't parse the output we display it in the console.
if (parsingError) {
if (execError != 0) {
AdtPlugin.printErrorToConsole(project, results.toArray());
} else {
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_ALWAYS, project,
results.toArray());
}
}
// We need to abort if the exec failed.
if (execError != 0) {
// if the exec failed, and we couldn't parse the error output (and therefore
// not all files that should have been marked, were marked), we put a generic
// marker on the project and abort.
if (parsingError) {
markProject(AdtConstants.MARKER_ADT, Messages.Unparsed_AAPT_Errors,
IMarker.SEVERITY_ERROR);
}
// abort if exec failed.
return false;
}
} catch (IOException e1) {
String msg = String.format(Messages.AAPT_Exec_Error, command[0]);
markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
return false;
} catch (InterruptedException e) {
String msg = String.format(Messages.AAPT_Exec_Error, command[0]);
markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
return false;
}
return true;
}
/**
* Execute the Dx tool for dalvik code conversion.
* @param javaProject The java project
* @param osBinPath the path to the output folder of the project
* @param osOutFilePath the path of the dex file to create.
* @param referencedJavaProjects the list of referenced projects for this project.
* @return
* @throws CoreException
*/
private boolean executeDx(IJavaProject javaProject, String osBinPath, String osOutFilePath,
IJavaProject[] referencedJavaProjects) throws CoreException {
// get the dex wrapper
DexWrapper wrapper = DexWrapper.getWrapper();
if (wrapper == null) {
if (DexWrapper.getStatus() == DexWrapper.LoadStatus.FAILED) {
throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
Messages.ApkBuilder_UnableBuild_Dex_Not_loaded));
} else {
// means we haven't loaded the dex jar yet.
// We set the project to be recompiled after dex is loaded.
AdtPlugin.getDefault().addPostDexProject(javaProject);
// and we exit silently
return false;
}
}
// make sure dx use the proper output streams.
// first make sure we actually have the streams available.
if (mOutStream == null) {
IProject project = getProject();
mOutStream = AdtPlugin.getOutPrintStream(project, DX_PREFIX);
mErrStream = AdtPlugin.getErrPrintStream(project, DX_PREFIX);
}
try {
// get the list of libraries to include with the source code
String[] libraries = getExternalJars();
// get the list of referenced projects output to add
String[] projectOutputs = getProjectOutputs(referencedJavaProjects);
String[] fileNames = new String[1 + projectOutputs.length + libraries.length];
// first this project output
fileNames[0] = osBinPath;
// then other project output
System.arraycopy(projectOutputs, 0, fileNames, 1, projectOutputs.length);
// then external jars.
System.arraycopy(libraries, 0, fileNames, 1 + projectOutputs.length, libraries.length);
int res = wrapper.run(osOutFilePath, fileNames,
AdtPlugin.getBuildVerbosity() == AdtConstants.BUILD_VERBOSE,
mOutStream, mErrStream);
if (res != 0) {
// output error message and marker the project.
String message = String.format(Messages.Dalvik_Error_d,
res);
AdtPlugin.printErrorToConsole(getProject(), message);
markProject(AdtConstants.MARKER_ADT, message, IMarker.SEVERITY_ERROR);
return false;
}
} catch (Throwable ex) {
String message = String.format(Messages.Dalvik_Error_s,
ex.getMessage());
AdtPlugin.printErrorToConsole(getProject(), message);
markProject(AdtConstants.MARKER_ADT, message, IMarker.SEVERITY_ERROR);
if ((ex instanceof NoClassDefFoundError)
|| (ex instanceof NoSuchMethodError)) {
AdtPlugin.printErrorToConsole(getProject(), Messages.Incompatible_VM_Warning,
Messages.Requires_1_5_Error);
}
return false;
}
return true;
}
/**
* Makes the final package. Package the dex files, the temporary resource file into the final
* package file.
* @param intermediateApk The path to the temporary resource file.
* @param dex The path to the dex file.
* @param output The path to the final package file to create.
* @param javaProject
* @param referencedJavaProjects
* @return true if success, false otherwise.
*/
private boolean finalPackage(String intermediateApk, String dex, String output,
final IJavaProject javaProject, IJavaProject[] referencedJavaProjects) {
FileOutputStream fos = null;
try {
IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
String osKeyPath = store.getString(AdtPlugin.PREFS_CUSTOM_DEBUG_KEYSTORE);
if (osKeyPath == null || new File(osKeyPath).exists() == false) {
osKeyPath = DebugKeyProvider.getDefaultKeyStoreOsPath();
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(),
Messages.ApkBuilder_Using_Default_Key);
} else {
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(),
String.format(Messages.ApkBuilder_Using_s_To_Sign, osKeyPath));
}
// TODO: get the store type from somewhere else.
DebugKeyProvider provider = new DebugKeyProvider(null /* storeType */,
new IKeyGenOutput() {
public void err(String message) {
AdtPlugin.printErrorToConsole(javaProject.getProject(),
Messages.ApkBuilder_Signing_Key_Creation_s + message);
}
public void out(String message) {
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE,
javaProject.getProject(),
Messages.ApkBuilder_Signing_Key_Creation_s + message);
}
});
PrivateKey key = provider.getDebugKey();
X509Certificate certificate = (X509Certificate)provider.getCertificate();
if (key == null) {
String msg = String.format(Messages.Final_Archive_Error_s,
Messages.ApkBuilder_Unable_To_Gey_Key);
AdtPlugin.printErrorToConsole(javaProject.getProject(), msg);
markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
return false;
}
// compare the certificate expiration date
if (certificate != null && certificate.getNotAfter().compareTo(new Date()) < 0) {
// TODO, regenerate a new one.
String msg = String.format(Messages.Final_Archive_Error_s,
String.format(Messages.ApkBuilder_Certificate_Expired_on_s,
DateFormat.getInstance().format(certificate.getNotAfter())));
AdtPlugin.printErrorToConsole(javaProject.getProject(), msg);
markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
return false;
}
// create the jar builder.
fos = new FileOutputStream(output);
SignedJarBuilder builder = new SignedJarBuilder(fos, key, certificate);
// add the intermediate file containing the compiled resources.
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(),
String.format(Messages.ApkBuilder_Packaging_s, intermediateApk));
FileInputStream fis = new FileInputStream(intermediateApk);
try {
builder.writeZip(fis, null /* filter */);
} finally {
fis.close();
}
// Now we add the new file to the zip archive for the classes.dex file.
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(),
String.format(Messages.ApkBuilder_Packaging_s, AndroidConstants.FN_CLASSES_DEX));
File entryFile = new File(dex);
builder.writeFile(entryFile, AndroidConstants.FN_CLASSES_DEX);
// Now we write the standard resources from the project and the referenced projects.
writeStandardResources(builder, javaProject, referencedJavaProjects);
// Now we write the standard resources from the external libraries
for (String libraryOsPath : getExternalJars()) {
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(),
String.format(Messages.ApkBuilder_Packaging_s, libraryOsPath));
try {
fis = new FileInputStream(libraryOsPath);
builder.writeZip(fis, mJavaResourcesFilter);
} finally {
fis.close();
}
}
// close the jar file and write the manifest and sign it.
builder.close();
} catch (GeneralSecurityException e1) {
// mark project and return
String msg = String.format(Messages.Final_Archive_Error_s, e1.getMessage());
AdtPlugin.printErrorToConsole(javaProject.getProject(), msg);
markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
return false;
} catch (IOException e1) {
// mark project and return
String msg = String.format(Messages.Final_Archive_Error_s, e1.getMessage());
AdtPlugin.printErrorToConsole(javaProject.getProject(), msg);
markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
return false;
} catch (KeytoolException e) {
String eMessage = e.getMessage();
// mark the project with the standard message
String msg = String.format(Messages.Final_Archive_Error_s, eMessage);
markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
// output more info in the console
AdtPlugin.printErrorToConsole(javaProject.getProject(),
msg,
String.format(Messages.ApkBuilder_JAVA_HOME_is_s, e.getJavaHome()),
Messages.ApkBuilder_Update_or_Execute_manually_s,
e.getCommandLine());
} catch (AndroidLocationException e) {
String eMessage = e.getMessage();
// mark the project with the standard message
String msg = String.format(Messages.Final_Archive_Error_s, eMessage);
markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
// and also output it in the console
AdtPlugin.printErrorToConsole(javaProject.getProject(), msg);
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
// pass.
}
}
}
return true;
}
/**
* Writes the standard resources of a project and its referenced projects
* into a {@link SignedJarBuilder}.
* Standard resources are non java/aidl files placed in the java package folders.
* @param jarBuilder the {@link SignedJarBuilder}.
* @param javaProject the javaProject object.
* @param referencedJavaProjects the java projects that this project references.
* @throws IOException
*/
private void writeStandardResources(SignedJarBuilder jarBuilder, IJavaProject javaProject,
IJavaProject[] referencedJavaProjects) throws IOException {
IWorkspace ws = ResourcesPlugin.getWorkspace();
IWorkspaceRoot wsRoot = ws.getRoot();
// create a list of path already put into the archive, in order to detect conflict
ArrayList<String> list = new ArrayList<String>();
writeStandardProjectResources(jarBuilder, javaProject, wsRoot, list);
for (IJavaProject referencedJavaProject : referencedJavaProjects) {
writeStandardProjectResources(jarBuilder, referencedJavaProject, wsRoot, list);
}
}
/**
* Writes the standard resources of a {@link IJavaProject} into a {@link SignedJarBuilder}.
* Standard resources are non java/aidl files placed in the java package folders.
* @param jarBuilder the {@link SignedJarBuilder}.
* @param javaProject the javaProject object.
* @param wsRoot the {@link IWorkspaceRoot}.
* @param list a list of files already added to the archive, to detect conflicts.
* @throws IOException
*/
private void writeStandardProjectResources(SignedJarBuilder jarBuilder,
IJavaProject javaProject, IWorkspaceRoot wsRoot, ArrayList<String> list)
throws IOException {
// get the source pathes
ArrayList<IPath> sourceFolders = BaseProjectHelper.getSourceClasspaths(javaProject);
// loop on them and then recursively go through the content looking for matching files.
for (IPath sourcePath : sourceFolders) {
IResource sourceResource = wsRoot.findMember(sourcePath);
if (sourceResource != null && sourceResource.getType() == IResource.FOLDER) {
writeStandardSourceFolderResources(jarBuilder, sourcePath, (IFolder)sourceResource,
list);
}
}
}
/**
* Recursively writes the standard resources of a source folder into a {@link SignedJarBuilder}.
* Standard resources are non java/aidl files placed in the java package folders.
* @param jarBuilder the {@link SignedJarBuilder}.
* @param sourceFolder the {@link IPath} of the source folder.
* @param currentFolder The current folder we're recursively processing.
* @param list a list of files already added to the archive, to detect conflicts.
* @throws IOException
*/
private void writeStandardSourceFolderResources(SignedJarBuilder jarBuilder, IPath sourceFolder,
IFolder currentFolder, ArrayList<String> list) throws IOException {
try {
IResource[] members = currentFolder.members();
for (IResource member : members) {
int type = member.getType();
if (type == IResource.FILE && member.exists()) {
if (checkFileForPackaging((IFile)member)) {
// this files must be added to the archive.
IPath fullPath = member.getFullPath();
// We need to create its path inside the archive.
// This path is relative to the source folder.
IPath relativePath = fullPath.removeFirstSegments(
sourceFolder.segmentCount());
String zipPath = relativePath.toString();
// lets check it's not already in the list of path added to the archive
if (list.indexOf(zipPath) != -1) {
AdtPlugin.printErrorToConsole(getProject(),
String.format(
Messages.ApkBuilder_s_Conflict_with_file_s,
fullPath, zipPath));
} else {
// get the File object
File entryFile = member.getLocation().toFile();
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(),
String.format(Messages.ApkBuilder_Packaging_s_into_s, fullPath, zipPath));
// write it in the zip archive
jarBuilder.writeFile(entryFile, zipPath);
// and add it to the list of entries
list.add(zipPath);
}
}
} else if (type == IResource.FOLDER) {
if (checkFolderForPackaging((IFolder)member)) {
writeStandardSourceFolderResources(jarBuilder, sourceFolder,
(IFolder)member, list);
}
}
}
} catch (CoreException e) {
// if we can't get the members of the folder, we just don't do anything.
}
}
/**
* Returns the list of the output folders for the specified {@link IJavaProject} objects.
* @param referencedJavaProjects the java projects.
* @return an array, always. Can be empty.
* @throws CoreException
*/
private String[] getProjectOutputs(IJavaProject[] referencedJavaProjects) throws CoreException {
ArrayList<String> list = new ArrayList<String>();
IWorkspace ws = ResourcesPlugin.getWorkspace();
IWorkspaceRoot wsRoot = ws.getRoot();
for (IJavaProject javaProject : referencedJavaProjects) {
// get the output folder
IPath path = null;
try {
path = javaProject.getOutputLocation();
} catch (JavaModelException e) {
continue;
}
IResource outputResource = wsRoot.findMember(path);
if (outputResource != null && outputResource.getType() == IResource.FOLDER) {
String outputOsPath = outputResource.getLocation().toOSString();
list.add(outputOsPath);
}
}
return list.toArray(new String[list.size()]);
}
/**
* Returns an array of {@link IJavaProject} matching the provided {@link IProject} objects.
* @param projects the IProject objects.
* @return an array, always. Can be empty.
* @throws CoreException
*/
private IJavaProject[] getJavaProjects(IProject[] projects) throws CoreException {
ArrayList<IJavaProject> list = new ArrayList<IJavaProject>();
for (IProject p : projects) {
if (p.isOpen() && p.hasNature(JavaCore.NATURE_ID)) {
list.add(JavaCore.create(p));
}
}
return list.toArray(new IJavaProject[list.size()]);
}
/**
* Checks a {@link IFile} to make sure it should be packaged as standard resources.
* @param file the IFile representing the file.
* @return true if the file should be packaged as standard java resources.
*/
static boolean checkFileForPackaging(IFile file) {
String name = file.getName();
String ext = file.getFileExtension();
return JavaResourceFilter.checkFileForPackaging(name, ext);
}
/**
* Checks whether an {@link IFolder} and its content is valid for packaging into the .apk as
* standard Java resource.
* @param folder the {@link IFolder} to check.
*/
static boolean checkFolderForPackaging(IFolder folder) {
String name = folder.getName();
return JavaResourceFilter.checkFolderForPackaging(name);
}
}

View File

@@ -0,0 +1,253 @@
/*
* 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 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>
* </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;
/**
* 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(AndroidConstants.FD_ASSETS);
if (assetFolder != null) {
mAssetPath = assetFolder.getFullPath();
}
IResource resFolder = builder.getProject().findMember(AndroidConstants.FD_RESOURCES);
if (resFolder != null) {
mResPath = resFolder.getFullPath();
}
}
public boolean getConvertToDex() {
return mConvertToDex;
}
public boolean getPackageResources() {
return mPackageResources;
}
public boolean getMakeFinalPackage() {
return mMakeFinalPackage;
}
/*
* (non-Javadoc)
*
* @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)) {
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();
if (resourceName.equalsIgnoreCase(AndroidConstants.FN_CLASSES_DEX)) {
mConvertToDex = true;
mMakeFinalPackage = true;
} else if (resourceName.equalsIgnoreCase(
AndroidConstants.FN_RESOURCES_AP_)) {
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 {
// 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,851 @@
/*
* 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.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 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.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>
* " (Occured 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>
* " (Occured 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$
/** 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 file the file to be marked
* @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 container 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, 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,
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, 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, 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, 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,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, 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,
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
* @param lineStr
* @param message
* @param root The root directory of the project, in OS specific format.
* @param project
* @param severity The severity of the marker to put (IMarker.SEVERITY_*)
* @return true if the parameters were valid and the file was marked
* sucessfully.
*
* @see IMarker
*/
private final boolean checkAndMark(String location, String lineStr,
String message, String root, IProject project, int severity) {
// check this is in fact a file
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 = 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(AndroidConstants.MARKER_AAPT, 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, AndroidConstants.MARKER_AAPT, message, line,
severity);
} else {
BaseProjectHelper.addMarker(f2, AndroidConstants.MARKER_AAPT, 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();
// get the file name. if it's the framework jar, we ignore that file.
// since we now use classpath container, this is here for legacy purpose only.
if (AndroidConstants.FN_FRAMEWORK_LIBRARY.equals(path.lastSegment())) {
continue;
}
// check the name ends with .jar
if (AndroidConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) {
boolean local = false;
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()]);
}
}

View File

@@ -0,0 +1,207 @@
/*
* 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 static DexWrapper sWrapper;
/** Status for the Loading of the dex jar file */
public enum LoadStatus { LOADING, LOADED, FAILED }
private static LoadStatus sLoadStatus = LoadStatus.LOADING;
private Method mRunMethod;
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 with the
* {@link DexWrapper} object returned by {@link #getWrapper()}
* @param osFilepath the location of the dex.jar file.
* @return an IStatus indicating the result of the load.
*/
public static synchronized IStatus loadDex(String osFilepath) {
try {
sWrapper = null;
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);
sWrapper = new DexWrapper(mainClass, argClass, consoleClass);
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);
} catch (CoreException e) {
return e.getStatus();
} finally {
if (sWrapper == null) {
sLoadStatus = LoadStatus.FAILED;
} else {
sLoadStatus = LoadStatus.LOADED;
}
}
}
/**
* Unloads the loaded dex wrapper.
*/
public static synchronized void unloadDex() {
sWrapper = null;
sLoadStatus = LoadStatus.LOADING;
}
public static synchronized DexWrapper getWrapper() {
return sWrapper;
}
/**
* Returns the {@link LoadStatus}.
*/
public static synchronized LoadStatus getStatus() {
return sLoadStatus;
}
/**
* 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 DexWrapper(Class<?> mainClass, Class<?> argClass, Class<?> consoleClass)
throws CoreException {
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) {
throw new CoreException(createErrorStatus(
Messages.DexWrapper_SecuryEx_Unable_To_Find_API, e));
} catch (NoSuchMethodException e) {
throw new CoreException(createErrorStatus(
Messages.DexWrapper_SecuryEx_Unable_To_Find_Method, e));
} catch (NoSuchFieldException e) {
throw new CoreException(createErrorStatus(
Messages.DexWrapper_SecuryEx_Unable_To_Find_Field, 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,412 @@
/*
* 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 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 {
/**
* 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;
/**
* 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;
/** List of .aidl files found that are modified or new. */
private final ArrayList<IFile> mAidlToCompile = new ArrayList<IFile>();
/** List of .aidl files that have been removed. */
private final ArrayList<IFile> mAidlToRemove = new ArrayList<IFile>();
private boolean mCheckedManifestXml = false;
private String mJavaPackage = null;
public PreCompilerDeltaVisitor(BaseBuilder builder, ArrayList<IPath> sourceFolders) {
super(builder);
mSourceFolders = sourceFolders;
}
public boolean getCompileResources() {
return mCompileResources;
}
public ArrayList<IFile> getAidlToCompile() {
return mAidlToCompile;
}
public ArrayList<IFile> getAidlToRemove() {
return mAidlToRemove;
}
/**
* 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.
* <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 (AndroidConstants.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;
}
}
// at this point we can either be in the source folder or in the
// resource foler or in a different folder that contains a source
// 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 every time.
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;
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,100 @@
/*
* 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.common.AndroidConstants;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IncrementalProjectBuilder;
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 IncrementalProjectBuilder {
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;
return null;
case ProjectHelper.COMPILER_COMPLIANCE_SOURCE:
errorMessage = Messages.Requires_Source_Compatibility_5;
return null;
case ProjectHelper.COMPILER_COMPLIANCE_CODEGEN_TARGET:
errorMessage = Messages.Requires_Class_Compatibility_5;
return null;
}
if (errorMessage != null) {
BaseProjectHelper.addMarker(project, AdtConstants.MARKER_ADT, errorMessage,
IMarker.SEVERITY_ERROR);
AdtPlugin.printErrorToConsole(project, errorMessage);
}
// 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,521 @@
/*
* 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.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.debug.launching.AndroidLaunchController.AndroidLaunchConfiguration;
import com.android.ide.eclipse.adt.debug.launching.AndroidLaunchController.DelayedLaunchInfo;
import com.android.ide.eclipse.ddms.DdmsPlugin;
import org.eclipse.core.resources.IProject;
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.graphics.Rectangle;
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.Dialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener {
private final static int DLG_WIDTH = 500;
private final static int DLG_HEIGHT = 300;
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_BUILD = "deviceChooser.build"; //$NON-NLS-1$
private Table mDeviceTable;
private TableViewer mViewer;
private Image mDeviceImage;
private Image mEmulatorImage;
private Button mOkButton;
private Button mCreateButton;
private DeviceChooserResponse mResponse;
private DelayedLaunchInfo mLaunchInfo;
/**
* 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 (columnIndex == 0 && element instanceof Device) {
if (((Device)element).isEmulator()) {
return mEmulatorImage;
}
return mDeviceImage;
}
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:
return getStateString(device);
case 2:
String debuggable = device.getProperty(Device.PROP_DEBUGGABLE);
String version = device.getProperty(Device.PROP_BUILD_VERSION);
if (debuggable != null && debuggable.equals("1")) { //$NON-NLS-1$
return String.format("%1$s (debug)", version); //$NON-NLS-1$
} else {
return String.format("%1$s", version); //$NON-NLS-1$
}
}
}
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 {
public boolean mustContinue;
public boolean mustLaunchEmulator;
public Device deviceToUse;
}
public DeviceChooserDialog(Shell parent) {
super(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL);
}
/**
* Prepare and display the dialog.
* @param response
* @param project
* @param launch
* @param launchInfo
* @param config
*/
public void open(DeviceChooserResponse response, IProject project,
AndroidLaunch launch, DelayedLaunchInfo launchInfo, AndroidLaunchConfiguration config) {
mResponse = response;
mLaunchInfo = launchInfo;
Shell parent = getParent();
Shell shell = new Shell(parent, getStyle());
shell.setText("Device Chooser");
loadImages();
createContents(shell);
// Set the dialog size.
shell.setMinimumSize(DLG_WIDTH, DLG_HEIGHT);
Rectangle r = parent.getBounds();
// get the center new top left.
int cx = r.x + r.width/2;
int x = cx - DLG_WIDTH / 2;
int cy = r.y + r.height/2;
int y = cy - DLG_HEIGHT / 2;
shell.setBounds(x, y, DLG_WIDTH, DLG_HEIGHT);
shell.pack();
shell.open();
// start the listening.
AndroidDebugBridge.addDeviceChangeListener(this);
Display display = parent.getDisplay();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
// done listening.
AndroidDebugBridge.removeDeviceChangeListener(this);
mEmulatorImage.dispose();
mDeviceImage.dispose();
AndroidLaunchController.getInstance().continueLaunch(response, project, launch,
launchInfo, config);
}
/**
* Create the device chooser dialog contents.
* @param shell the parent shell.
*/
private void createContents(final Shell shell) {
shell.setLayout(new GridLayout(1, true));
shell.addListener(SWT.Close, new Listener() {
public void handleEvent(Event event) {
event.doit = true;
}
});
Label l = new Label(shell, SWT.NONE);
l.setText("Select the target device.");
IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
mDeviceTable = new Table(shell, SWT.SINGLE | SWT.FULL_SELECTION);
mDeviceTable.setLayoutData(new GridData(GridData.FILL_BOTH));
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, "State",
SWT.LEFT, "bootloader", //$NON-NLS-1$
PREFS_COL_STATE, store);
TableHelper.createTableColumn(mDeviceTable, "Build Info",
SWT.LEFT, "engineering", //$NON-NLS-1$
PREFS_COL_BUILD, 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) {
Device selectedDevice = (Device)object;
mResponse.deviceToUse = selectedDevice;
mResponse.mustContinue = true;
shell.close();
}
}
}
});
// bottom part with the ok/cancel
Composite bottomComp = new Composite(shell, SWT.NONE);
bottomComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
// 3 items in the layout: createButton, spacer, composite with ok/cancel
// (to force same width).
bottomComp.setLayout(new GridLayout(3 /* numColums */, false /* makeColumnsEqualWidth */));
mCreateButton = new Button(bottomComp, SWT.NONE);
mCreateButton.setText("Launch Emulator");
mCreateButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
mResponse.mustContinue = true;
mResponse.mustLaunchEmulator = true;
shell.close();
}
});
// the spacer
Composite spacer = new Composite(bottomComp, SWT.NONE);
GridData gd;
spacer.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.heightHint = 0;
// the composite to contain ok/cancel
Composite buttonContainer = new Composite(bottomComp, SWT.NONE);
GridLayout gl = new GridLayout(2 /* numColums */, true /* makeColumnsEqualWidth */);
gl.marginHeight = gl.marginWidth = 0;
buttonContainer.setLayout(gl);
mOkButton = new Button(buttonContainer, SWT.NONE);
mOkButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mOkButton.setEnabled(false);
mOkButton.setText("OK");
mOkButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
mResponse.mustContinue = true;
shell.close();
}
});
Button cancelButton = new Button(buttonContainer, SWT.NONE);
cancelButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
cancelButton.setText("Cancel");
cancelButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
mResponse.mustContinue = false;
shell.close();
}
});
mDeviceTable.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
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);
}
}
}
});
mDeviceTable.setFocus();
shell.setDefaultButton(mOkButton);
updateDefaultSelection();
}
private void loadImages() {
IImageLoader loader = DdmsPlugin.getImageLoader();
Display display = DdmsPlugin.getDisplay();
if (mDeviceImage == null) {
mDeviceImage = ImageHelper.loadImage(loader, display,
"device.png", //$NON-NLS-1$
ICON_WIDTH, ICON_WIDTH,
display.getSystemColor(SWT.COLOR_RED));
}
if (mEmulatorImage == null) {
mEmulatorImage = ImageHelper.loadImage(loader, display,
"emulator.png", ICON_WIDTH, ICON_WIDTH, //$NON-NLS-1$
display.getSystemColor(SWT.COLOR_BLUE));
}
}
/**
* 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();
} 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)
*/
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();
// if the changed device is the current selection,
// we update the OK button based on its state.
if (device == mResponse.deviceToUse) {
mOkButton.setEnabled(mResponse.deviceToUse.isOnline());
}
} else {
// table is disposed, we need to do something.
// lets remove ourselves from the listener.
AndroidDebugBridge.removeDeviceChangeListener(dialog);
}
}
});
}
}
/**
* 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 handleSelection(Device device) {
mResponse.deviceToUse = device;
mOkButton.setEnabled(device != null && mResponse.deviceToUse.isOnline());
}
/**
* 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, until there is a 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 (mLaunchInfo.mPackageName.equals(
client.getClientData().getClientDescription())) {
// found a match! Select it.
mViewer.setSelection(new StructuredSelection(device));
handleSelection(device);
// and we're done.
return;
}
}
}
}
}
}

View File

@@ -0,0 +1,435 @@
/*
* 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$
/** Skin to be used to launch the emulator */
public static final String ATTR_SKIN = AdtPlugin.PLUGIN_ID + ".skin"; //$NON-NLS-1$
/**
* Name of the default Skin.
*/
public static final String DEFAULT_SKIN = "HVGA"; //$NON-NLS-1$
public static final String ATTR_SPEED = AdtPlugin.PLUGIN_ID + ".speed"; //$NON-NLS-1$
/**
* 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(configuration,
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(), 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 */));
}
@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.
* @param configuration
* @return
*/
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,413 @@
/*
* 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.debug.ui.SkinRepository.Skin;
import com.android.ide.eclipse.ddms.DdmsPlugin;
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.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 Combo mSpeedCombo;
private Combo mDelayCombo;
private Group mEmulatorOptionsGroup;
private Text mEmulatorCLOptions;
private Combo mSkinCombo;
private Button mAutoTargetButton;
private Button mManualTargetButton;
private Button mWipeDataButton;
private Button mNoBootAnimButton;
/**
* 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);
// 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();
}
});
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.
// 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);
new Label(mEmulatorOptionsGroup, SWT.NONE).setText("Screen Size:");
mSkinCombo = new Combo(mEmulatorOptionsGroup, SWT.READ_ONLY);
Skin[] skins = SkinRepository.getInstance().getSkins();
if (skins != null) {
for (Skin skin : skins) {
mSkinCombo.add(skin.getDescription());
}
}
mSkinCombo.addSelectionListener(new SelectionAdapter() {
// called when selection changes
@Override
public void widgetSelected(SelectionEvent e) {
updateLaunchConfigurationDialog();
}
});
mSkinCombo.pack();
// 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) {
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);
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;
try {
String skin = configuration.getAttribute(LaunchConfigDelegate.ATTR_SKIN, (String)null);
if (skin != null) {
index = getSkinIndex(skin);
}
} catch (CoreException e) {
index = getSkinIndex(SkinRepository.getInstance().checkSkin(
LaunchConfigDelegate.DEFAULT_SKIN));
}
if (index == -1) {
mSkinCombo.clearSelection();
} else {
mSkinCombo.select(index);
}
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());
configuration.setAttribute(LaunchConfigDelegate.ATTR_SKIN,
getSkinNameByIndex(mSkinCombo.getSelectionIndex()));
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_SKIN,
LaunchConfigDelegate.DEFAULT_SKIN);
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);
}
private String getSkinNameByIndex(int index) {
return SkinRepository.getInstance().getSkinNameByIndex(index);
}
private int getSkinIndex(String name) {
return SkinRepository.getInstance().getSkinIndex(name);
}
}

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,487 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.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 store the
* activities in <code>mActivities</code>
* First activity is selected by default if present.
* @param project The project to load the activities from
* @return The array of activities or null if none could be found.
*/
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,152 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.debug.ui;
import com.android.ide.eclipse.common.AndroidConstants;
import java.io.File;
import java.util.ArrayList;
/**
* Repository for the emulator skins. This class is responsible for parsing the skin folder.
*/
public class SkinRepository {
private final static SkinRepository sInstance = new SkinRepository();
private Skin[] mSkins;
public static class Skin {
String mName;
public Skin(String name) {
mName = name;
}
public String getName() {
return mName;
}
/**
* Returns the human readable description of the skin.
*/
public String getDescription() {
// TODO: parse the skin and output the description.
return mName;
}
}
/**
* Returns the singleton instance.
*/
public static SkinRepository getInstance() {
return sInstance;
}
/**
* Parse the skin folder and build the skin list.
* @param osPath The path of the skin folder.
*/
public void parseFolder(String osPath) {
File skinFolder = new File(osPath);
if (skinFolder.isDirectory()) {
ArrayList<Skin> skinList = new ArrayList<Skin>();
File[] files = skinFolder.listFiles();
for (File skin : files) {
if (skin.isDirectory()) {
// check for layout file
File layout = new File(skin.getPath() + File.separator
+ AndroidConstants.FN_LAYOUT);
if (layout.isFile()) {
// for now we don't parse the content of the layout and
// simply add the directory to the list.
skinList.add(new Skin(skin.getName()));
}
}
}
mSkins = skinList.toArray(new Skin[skinList.size()]);
} else {
mSkins = new Skin[0];
}
}
public Skin[] getSkins() {
return mSkins;
}
/**
* Returns a valid skin folder name. If <code>skin</code> is valid, then it is returned,
* otherwise the first valid skin name is returned.
* @param skin the Skin name to check
* @return a valid skin name or null if there aren't any.
*/
public String checkSkin(String skin) {
if (mSkins != null) {
for (Skin s : mSkins) {
if (s.getName().equals(skin)) {
return skin;
}
}
if (mSkins.length > 0) {
return mSkins[0].getName();
}
}
return null;
}
/**
* Returns the name of a skin by index.
* @param index The index of the skin to return
* @return the skin name of null if the index is invalid.
*/
public String getSkinNameByIndex(int index) {
if (mSkins != null) {
if (index >= 0 && index < mSkins.length) {
return mSkins[index].getName();
}
}
return null;
}
/**
* Returns the index (0 based) of the skin matching the name.
* @param name The name of the skin to look for.
* @return the index of the skin or -1 if the skin was not found.
*/
public int getSkinIndex(String name) {
if (mSkins != null) {
int count = mSkins.length;
for (int i = 0 ; i < count ; i++) {
Skin s = mSkins[i];
if (s.mName.equals(name)) {
return i;
}
}
}
return -1;
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.editors.java;
import org.eclipse.jdt.ui.PreferenceConstants;
import org.eclipse.jdt.ui.text.IJavaPartitions;
import org.eclipse.jdt.ui.text.JavaSourceViewerConfiguration;
import org.eclipse.jdt.ui.text.JavaTextTools;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditor;
/**
* Read only java editors. This looks like the regular Java editor, except that it
* prevents editing the files. This is used for automatically generated java classes.
*/
public class ReadOnlyJavaEditor extends AbstractDecoratedTextEditor {
public final static String ID =
"com.android.ide.eclipse.adt.editors.java.ReadOnlyJavaEditor"; //$NON-NLS-1$
public ReadOnlyJavaEditor() {
IPreferenceStore javaUiStore = PreferenceConstants.getPreferenceStore();
JavaTextTools jtt = new JavaTextTools(javaUiStore);
JavaSourceViewerConfiguration jsvc = new JavaSourceViewerConfiguration(
jtt.getColorManager(), javaUiStore, this, IJavaPartitions.JAVA_PARTITIONING);
setSourceViewerConfiguration(jsvc);
}
@Override
public boolean isEditable() {
return false;
}
}

View File

@@ -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,135 @@
/*
* 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 org.eclipse.jface.preference.DirectoryFieldEditor;
import org.eclipse.jface.preference.FieldEditorPreferencePage;
import org.eclipse.jface.resource.JFaceResources;
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;
/**
* 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 {
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);
}
}
}

View File

@@ -0,0 +1,108 @@
/*
* 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 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;
/**
* 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 FileFieldEditor(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);
}
}
}

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,209 @@
/*
* 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 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("/project.aidl"); //$NON-NLS-1$
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 ("android.os.Parcelable".equals(superInterface.getFullyQualifiedName())) { //$NON-NLS-1$
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,701 @@
/*
* 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.IClasspathAttribute;
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;
}
/**
* Check the validity of the javadoc attributes in a classpath entry.
* @param frameworkEntry the classpath entry to check.
* @return true if the javadoc attributes is valid, false otherwise.
*/
public static boolean checkJavaDocPath(IClasspathEntry frameworkEntry) {
// get the list of extra attributes
IClasspathAttribute[] attributes = frameworkEntry.getExtraAttributes();
// and search for the one about the javadoc
for (IClasspathAttribute att : attributes) {
if (IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME.
equals(att.getName())) {
// we found a javadoc attribute. Now we test its value.
// get the expect value
String validJavaDoc = getJavaDocPath(AdtPlugin.getOsAbsoluteFramework());
// now compare and return
return validJavaDoc.equals(att.getValue());
}
}
return false;
}
/**
* Fix the project. This checks the SDK location.
* @param project The project to fix.
* @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) {
IPath containerPath = entry.getPath();
if (AndroidClasspathContainerInitializer.checkOldPath(containerPath)) {
entries = ProjectHelper.removeEntryFromClasspath(entries, i);
// continue, to skip the i++;
continue;
} else if (AndroidClasspathContainerInitializer.checkPath(containerPath)) {
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 VM.
* <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,254 @@
/*
* 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.SignedJarBuilder;
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.ui.IExportWizard;
import org.eclipse.ui.IWorkbench;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
/**
* Export wizard to export an apk signed with a release key/certificate.
*/
public class ExportWizard extends Wizard implements IExportWizard {
private static final String PROJECT_LOGO_LARGE = "icons/android_large.png"; //$NON-NLS-1$
private static final String PAGE_PRE = "preExportPage"; //$NON-NLS-1$
private static final String PAGE_SIGNING = "signingExportPage"; //$NON-NLS-1$
private static final String PAGE_FINAL = "finalExportPage"; //$NON-NLS-1$
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 {
protected boolean mNewProjectReference = true;
ExportWizardPage(String name) {
super(name);
}
abstract void onShow();
@Override
public void setVisible(boolean visible) {
super.setVisible(visible);
if (visible) {
onShow();
mNewProjectReference = false;
}
}
void newProjectReference() {
mNewProjectReference = true;
}
}
private ExportWizardPage mPages[] = new ExportWizardPage[3];
private IProject mProject;
private String mKeystore;
private String mKeyAlias;
private char[] mKeystorePassword;
private char[] mKeyPassword;
private PrivateKey mPrivateKey;
private X509Certificate mCertificate;
private String mDestinationPath;
private String mApkFilePath;
private String mApkFileName;
public ExportWizard() {
setHelpAvailable(false); // TODO have help
setWindowTitle("Export Android Application");
setImageDescriptor();
}
@Override
public void addPages() {
addPage(mPages[0] = new PreExportPage(this, PAGE_PRE));
addPage(mPages[1] = new SigningExportPage(this, PAGE_SIGNING));
addPage(mPages[2] = new FinalExportPage(this, PAGE_FINAL));
}
@Override
public boolean performFinish() {
// save the properties
ProjectHelper.saveStringProperty(mProject, PROPERTY_KEYSTORE, mKeystore);
ProjectHelper.saveStringProperty(mProject, PROPERTY_ALIAS, mKeyAlias);
ProjectHelper.saveStringProperty(mProject, PROPERTY_DESTINATION, mDestinationPath);
try {
FileOutputStream fos = new FileOutputStream(mDestinationPath);
SignedJarBuilder builder = new SignedJarBuilder(fos, mPrivateKey, mCertificate);
// get the input file.
FileInputStream fis = new FileInputStream(mApkFilePath);
try {
builder.writeZip(fis, null /* filter */);
} finally {
fis.close();
}
builder.close();
fos.close();
return true;
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return false;
}
@Override
public boolean canFinish() {
return mApkFilePath != null &&
mPrivateKey != null && mCertificate != null &&
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();
}
}
}
/**
* 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;
// indicate to the page that the project was changed.
for (ExportWizardPage page : mPages) {
page.newProjectReference();
}
}
String getApkFilename() {
return mApkFileName;
}
void setKeystore(String path) {
mKeystore = path;
mPrivateKey = null;
mCertificate = null;
}
String getKeystore() {
return mKeystore;
}
void setKeyAlias(String name) {
mKeyAlias = name;
mPrivateKey = null;
mCertificate = null;
}
String getKeyAlias() {
return mKeyAlias;
}
void setKeystorePassword(char[] password) {
mKeystorePassword = password;
mPrivateKey = null;
mCertificate = null;
}
char[] getKeystorePassword() {
return mKeystorePassword;
}
void setKeyPassword(char[] password) {
mKeyPassword = password;
mPrivateKey = null;
mCertificate = null;
}
char[] getKeyPassword() {
return mKeyPassword;
}
void setSigningInfo(PrivateKey privateKey, X509Certificate certificate) {
mPrivateKey = privateKey;
mCertificate = certificate;
}
void setDestination(String path) {
mDestinationPath = path;
}
}

View File

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

View File

@@ -0,0 +1,318 @@
/*
* 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.
*/
public class PreExportPage 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;
protected PreExportPage(ExportWizard wizard, String pageName) {
super(pageName);
mWizard = wizard;
setTitle("Pre Export 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() {
// get the project and init the ui
IProject project = mWizard.getProject();
if (project != null) {
mProjectText.setText(project.getName());
}
}
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.
* @return the current IProject of this launch config.
*/
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,218 @@
/*
* 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 java.io.File;
/**
* Second export wizard page.
*/
public class SigningExportPage extends ExportWizardPage {
private final ExportWizard mWizard;
private Text mKeystore;
private Text mAlias;
private Text mKeystorePassword;
private Text mKeyPassword;
private boolean mDisableOnChange = false;
protected SigningExportPage(ExportWizard wizard, String pageName) {
super(pageName);
mWizard = wizard;
setTitle("Application Signing");
setDescription("Defines which store, key and certificate to use to sign the Android Application.");
}
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;
new Label(composite, SWT.NONE).setText("Keystore:");
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 = new FileDialog(browseButton.getShell(), SWT.OPEN);
fileDialog.setText("Load Keystore");
String fileName = fileDialog.open();
if (fileName != null) {
mKeystore.setText(fileName);
}
}
});
new Composite(composite, SWT.NONE).setLayoutData(gd = new GridData());
gd.horizontalSpan = 2;
gd.heightHint = 0;
new Button(composite, SWT.PUSH).setText("New...");
new Label(composite, SWT.NONE).setText("Key Alias:");
mAlias = new Text(composite, SWT.BORDER);
mAlias.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL).setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalSpan = 3;
new Label(composite, SWT.NONE).setText("Store password:");
mKeystorePassword = new Text(composite, SWT.BORDER | SWT.PASSWORD);
mKeystorePassword.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
new Composite(composite, SWT.NONE).setLayoutData(gd = new GridData());
gd.heightHint = gd.widthHint = 0;
new Label(composite, SWT.NONE).setText("Key password:");
mKeyPassword = new Text(composite, SWT.BORDER | SWT.PASSWORD);
mKeyPassword.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
// Show description the first time
setErrorMessage(null);
setMessage(null);
setControl(composite);
mKeystore.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
mWizard.setKeystore(mKeystore.getText().trim());
onChange();
}
});
mAlias.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
mWizard.setKeyAlias(mAlias.getText().trim());
onChange();
}
});
mKeystorePassword.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
mWizard.setKeystorePassword(mKeystorePassword.getText().trim().toCharArray());
onChange();
}
});
mKeyPassword.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
mWizard.setKeyPassword(mKeyPassword.getText().trim().toCharArray());
onChange();
}
});
}
@Override
void onShow() {
// fill the texts with information loaded from the project.
if (mNewProjectReference) {
// 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);
}
String alias = ProjectHelper.loadStringProperty(project, ExportWizard.PROPERTY_ALIAS);
if (alias != null) {
mAlias.setText(alias);
}
// reset the passwords
mKeystorePassword.setText(""); //$NON-NLS-1$
mKeyPassword.setText(""); //$NON-NLS-1$
// 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);
// 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) {
setErrorMessage("Keystore does not exists!");
setPageComplete(false);
return;
} else if (f.isDirectory()) {
setErrorMessage("Keystore is a directory!");
setPageComplete(false);
return;
}
}
if (mAlias.getText().trim().length() == 0) {
setErrorMessage("Enter key alias.");
setPageComplete(false);
return;
}
if (mKeystorePassword.getText().trim().length() == 0) {
setErrorMessage("Enter keystore password.");
setPageComplete(false);
return;
}
if (mKeyPassword.getText().trim().length() == 0) {
setErrorMessage("Enter key password.");
setPageComplete(false);
return;
}
setPageComplete(true);
}
}

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.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;
/**
* Constructs the container with the {@link IClasspathEntry} representing the android
* framework jar file and the container id
* @param entry the entry representing the android framework.
* @param path the path containing the classpath container id.
*/
AndroidClasspathContainer(IClasspathEntry entry, IPath path) {
mClasspathEntry = new IClasspathEntry[] { entry };
mContainerPath = path;
}
public IClasspathEntry[] getClasspathEntries() {
return mClasspathEntry;
}
public String getDescription() {
return "Android Library";
}
public int getKind() {
return IClasspathContainer.K_DEFAULT_SYSTEM;
}
public IPath getPath() {
return mContainerPath;
}
}

View File

@@ -0,0 +1,198 @@
/*
* 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.AdtPlugin;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
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;
/**
* 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 old container id */
private final static String OLD_CONTAINER_ID =
"com.android.ide.eclipse.adt.project.AndroidClasspathContainerInitializer"; //$NON-NLS-1$
/** The container id for the android framework jar file */
private final static String CONTAINER_ID =
"com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"; //$NON-NLS-1$
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 {
String id = null;
if (OLD_CONTAINER_ID.equals(containerPath.toString())) {
id = OLD_CONTAINER_ID;
} else if (CONTAINER_ID.equals(containerPath.toString())) {
id = CONTAINER_ID;
}
if (id != null) {
JavaCore.setClasspathContainer(new Path(id),
new IJavaProject[] { project },
new IClasspathContainer[] { allocateAndroidContainer(id) },
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 old android framework container id and
* returns <code>true</code> if they are identical.
* @param path the <code>IPath</code> to check.
*/
public static boolean checkOldPath(IPath path) {
return OLD_CONTAINER_ID.equals(path.toString());
}
/**
* Checks the {@link IPath} objects against the android framework container id and
* returns <code>true</code> if they are identical.
* @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 {
// because those projects could have the old id, we are going to fix
// them dynamically here.
for (IJavaProject javaProject: androidProjects) {
IClasspathEntry[] entries = javaProject.getRawClasspath();
int containerIndex = ProjectHelper.findClasspathEntryByPath(entries,
OLD_CONTAINER_ID,
IClasspathEntry.CPE_CONTAINER);
if (containerIndex != -1) {
// the project has the old container, we remove it
entries = ProjectHelper.removeEntryFromClasspath(entries, containerIndex);
// we add the new one instead
entries = ProjectHelper.addEntryToClasspath(entries, getContainerEntry());
// and give the new entries to the project
javaProject.setRawClasspath(entries, new NullProgressMonitor());
}
}
// Allocate a new AndroidClasspathContainer, and associate it to the android framework
// container id for each projects.
// By providing a new association between a container id and a IClasspathContainer,
// this forces the JDT to query the IClasspathContainer for new IClasspathEntry (with
// IClasspathContainer#getClasspathEntries()), and therefore force recompilation of
// the projects.
// TODO: We could only do that for the projects haven't fixed above
// (this isn't something that will happen a lot though)
int projectCount = androidProjects.length;
IClasspathContainer[] containers = new IClasspathContainer[projectCount];
for (int i = 0 ; i < projectCount; i++) {
containers[i] = allocateAndroidContainer(CONTAINER_ID);
}
// 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 containerId the container id to be used.
*/
private static IClasspathContainer allocateAndroidContainer(String containerId) {
return new AndroidClasspathContainer(createFrameworkClasspath(), new Path(containerId));
}
/**
* Creates and returns a new {@link IClasspathEntry} object for the android framework.
* <p/>This references the OS path to the android.jar and the java doc directory. This is
* dynamically created when a project is opened, and never saved in the project itself, so
* there's no risk of storing an obsolete path.
*/
private static IClasspathEntry createFrameworkClasspath() {
// now add the android framework to the class path.
// create the path object.
IPath android_lib = new Path(AdtPlugin.getOsAbsoluteFramework());
IPath android_src = new Path(AdtPlugin.getOsAbsoluteAndroidSources());
// create the java doc link.
IClasspathAttribute cpAttribute = JavaCore.newClasspathAttribute(
IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME, AdtPlugin.getUrlDoc());
// 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 classpathEntry = JavaCore.newLibraryEntry(android_lib,
android_src, // source attachment path
null, // default source attachment root path.
new IAccessRule[] { accessRule },
new IClasspathAttribute[] { cpAttribute },
false // not exported.
);
return classpathEntry;
}
}

View File

@@ -0,0 +1,706 @@
/*
* 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.AdtPlugin;
import com.android.ide.eclipse.adt.project.AndroidNature;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.common.AndroidConstants;
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;
/**
* 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 PH_ACTIVITIES = "ACTIVITIES"; //$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 =
AndroidConstants.FD_BINARIES + AndroidConstants.WS_SEP;
private static final String RES_DIRECTORY =
AndroidConstants.FD_RESOURCES + AndroidConstants.WS_SEP;
private static final String ASSETS_DIRECTORY =
AndroidConstants.FD_ASSETS + AndroidConstants.WS_SEP;
private static final String DRAWABLE_DIRECTORY =
AndroidConstants.FD_DRAWABLE + AndroidConstants.WS_SEP;
private static final String LAYOUT_DIRECTORY =
AndroidConstants.FD_LAYOUT + AndroidConstants.WS_SEP;
private static final String VALUES_DIRECTORY =
AndroidConstants.FD_VALUES + 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_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, String> parameters = new HashMap<String, String>();
parameters.put(PARAM_PROJECT, mMainPage.getProjectName());
parameters.put(PARAM_PACKAGE, mMainPage.getPackageName());
parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME);
parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder());
parameters.put(PARAM_IS_NEW_PROJECT, Boolean.toString(mMainPage.isNewProject()));
parameters.put(PARAM_SRC_FOLDER, mMainPage.getSourceFolder());
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, String> 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[] sourceFolder = new String[] { parameters.get(PARAM_SRC_FOLDER) };
addDefaultDirectories(project, AndroidConstants.WS_ROOT, sourceFolder, monitor);
// Create the resource folders in the project if they don't already exist.
addDefaultDirectories(project, RES_DIRECTORY, RES_DIRECTORIES, monitor);
// Setup class path
IJavaProject javaProject = JavaCore.create(project);
setupSourceFolder(javaProject, sourceFolder[0], monitor);
if (Boolean.parseBoolean(parameters.get(PARAM_IS_NEW_PROJECT))) {
// 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, sourceFolder[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);
}
// 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, String> 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, "");
}
// 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, String> parameters, Map<String, String> stringDictionary,
IProgressMonitor monitor) throws CoreException, IOException {
// create the java package directories.
IFolder pkgFolder = project.getFolder(sourceFolder);
String packageName = parameters.get(PARAM_PACKAGE);
// The PARAM_ACTIVITY key will be absent if no activity should be created,
// in which case activityName will be null.
String activityName = parameters.get(PARAM_ACTIVITY);
Map<String, String> java_activity_parameters = parameters;
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, String>(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, String> 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, String> parameters) {
for (String key : parameters.keySet()) {
str = str.replaceAll(key, parameters.get(key));
}
return str;
}
}

View File

@@ -0,0 +1,425 @@
/*
* 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.resources;
import com.android.ide.eclipse.adt.resources.LayoutParamsParser.IClass;
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 IAndroidLoader {
public final static class ClassWrapper implements IClass {
private Class<?> mClass;
public ClassWrapper(Class<?> clazz) {
mClass = clazz;
}
public String getCanonicalName() {
return mClass.getCanonicalName();
}
public IClass[] getDeclaredClasses() {
Class<?>[] classes = mClass.getDeclaredClasses();
IClass[] iclasses = new IClass[classes.length];
for (int i = 0 ; i < classes.length ; i++) {
iclasses[i] = new ClassWrapper(classes[i]);
}
return iclasses;
}
public IClass getEnclosingClass() {
return new ClassWrapper(mClass.getEnclosingClass());
}
public String getSimpleName() {
return mClass.getSimpleName();
}
public IClass 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 monitor A progress monitor. Can be null. Caller is responsible for calling done.
* @throws IOException
* @throws InvalidAttributeValueException
* @throws ClassFormatError
*/
public void preLoadClasses(String packageFilter, IProgressMonitor monitor)
throws IOException, InvalidAttributeValueException, ClassFormatError {
// Transform the package name into a zip entry path
String pathFilter = packageFilter.replaceAll("\\.", "/"); //$NON-NLS-1$ //$NON-NLS-2$
SubMonitor progress = SubMonitor.convert(monitor, 100);
// 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);
}
}
/**
* 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<IClass>> findClassesDerivingFrom(
String packageFilter,
String[] superClasses)
throws IOException, InvalidAttributeValueException, ClassFormatError {
packageFilter = packageFilter.replaceAll("\\.", "/"); //$NON-NLS-1$ //$NON-NLS-2$
HashMap<String, ArrayList<IClass>> mClassesFound = new HashMap<String, ArrayList<IClass>>();
for (String className : superClasses) {
mClassesFound.put(className, new ArrayList<IClass>());
}
// 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 IClass} by its fully-qualified name.
* @param className the fully-qualified name of the class to return.
* @throws ClassNotFoundException
*/
public IClass getClass(String className) throws ClassNotFoundException {
try {
return new ClassWrapper(loadClass(className));
} catch (ClassNotFoundException e) {
throw e; // useful for debugging
}
}
}

View File

@@ -0,0 +1,576 @@
/*
* 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.resources;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.resources.AttrsXmlParser;
import com.android.ide.eclipse.common.resources.DeclareStyleableInfo;
import com.android.ide.eclipse.common.resources.FrameworkResourceManager;
import com.android.ide.eclipse.common.resources.ResourceItem;
import com.android.ide.eclipse.common.resources.ResourceType;
import com.android.ide.eclipse.common.resources.ViewClassInfo;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
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.Field;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
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 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 final class FrameworkResourceParser {
private static final String TAG = "Framework Resource Parser";
/**
* Creates a framework resource parser.
*/
public FrameworkResourceParser() {
}
/**
* Parses the framework, collects all interesting information and stores them in the
* {@link FrameworkResourceManager} given to the constructor.
*
* @param osSdkPath the OS path of the SDK directory.
* @param resourceManager the {@link FrameworkResourceManager} that will store the parsed
* resources.
* @param monitor A progress monitor. Can be null. Caller is responsible for calling done.
* @return True if the SDK path was valid and parsing has been attempted.
*/
public boolean parse(String osSdkPath, FrameworkResourceManager resourceManager,
IProgressMonitor monitor) {
if (osSdkPath == null || osSdkPath.length() == 0) {
return false;
}
try {
SubMonitor progress = SubMonitor.convert(monitor, 100);
AndroidJarLoader classLoader =
new AndroidJarLoader(osSdkPath + AndroidConstants.FN_FRAMEWORK_LIBRARY);
progress.subTask("Preloading");
preload(classLoader, progress.newChild(40));
progress.setWorkRemaining(60);
if (progress.isCanceled()) {
return false;
}
// get the resource Ids.
progress.subTask("Resource IDs");
FrameworkResourceRepository systemResourceRepository = new FrameworkResourceRepository(
collectResourceIds(classLoader));
progress.worked(5);
if (progress.isCanceled()) {
return false;
}
// get the permissions
progress.subTask("Permissions");
String[] permissionValues = collectPermissions(classLoader);
progress.worked(5);
if (progress.isCanceled()) {
return false;
}
String osLibPath = osSdkPath + AndroidConstants.OS_SDK_LIBS_FOLDER;
// get the action and category values for the Intents.
progress.subTask("Intents");
ArrayList<String> activity_actions = new ArrayList<String>();
ArrayList<String> broadcast_actions = new ArrayList<String>();
ArrayList<String> service_actions = new ArrayList<String>();
ArrayList<String> categories = new ArrayList<String>();
collectIntentFilterActionsAndCategories(osLibPath,
activity_actions, broadcast_actions, service_actions, categories);
progress.worked(5);
if (progress.isCanceled()) {
return false;
}
progress.subTask("Layouts");
AttrsXmlParser attrsXmlParser = new AttrsXmlParser(
osSdkPath + AndroidConstants.OS_SDK_ATTRS_XML);
attrsXmlParser.preload();
AttrsXmlParser attrsManifestXmlParser = new AttrsXmlParser(
osSdkPath + AndroidConstants.OS_SDK_ATTRS_MANIFEST_XML,
attrsXmlParser);
attrsManifestXmlParser.preload();
Collection<ViewClassInfo> mainList = new ArrayList<ViewClassInfo>();
Collection<ViewClassInfo> groupList = new ArrayList<ViewClassInfo>();
collectLayoutClasses(osLibPath, classLoader, attrsXmlParser, mainList, groupList,
progress.newChild(40));
if (progress.isCanceled()) {
return false;
}
ViewClassInfo[] layoutViewsInfo = mainList.toArray(new ViewClassInfo[mainList.size()]);
ViewClassInfo[] layoutGroupsInfo = groupList.toArray(
new ViewClassInfo[groupList.size()]);
mainList.clear();
groupList.clear();
collectPreferenceClasses(classLoader, attrsXmlParser, mainList, groupList,
progress.newChild(5));
if (progress.isCanceled()) {
return false;
}
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();
if (progress.isCanceled()) {
return false;
}
String docBaseUrl = getDocumentationBaseUrl(
osSdkPath + AndroidConstants.OS_SDK_DOCS_FOLDER);
FrameworkResourceManager.getInstance().setResources(systemResourceRepository,
layoutViewsInfo,
layoutGroupsInfo,
preferencesInfo,
preferenceGroupsInfo,
xmlMenuMap,
xmlSearchableMap,
manifestMap,
enumValueMap,
permissionValues,
activity_actions.toArray(new String[activity_actions.size()]),
broadcast_actions.toArray(new String[broadcast_actions.size()]),
service_actions.toArray(new String[service_actions.size()]),
categories.toArray(new String[categories.size()]),
docBaseUrl);
return true;
} catch (Exception e) {
AdtPlugin.logAndPrintError(e, TAG, "SDK parser failed"); //$NON-NLS-1$
}
return false;
}
/**
* 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 */, monitor); //$NON-NLS-1$
} catch (InvalidAttributeValueException e) {
AdtPlugin.log(e, "Problem preloading classes"); //$NON-NLS-1$
} catch (IOException e) {
AdtPlugin.log(e, "Problem preloading classes"); //$NON-NLS-1$
}
}
/**
* Collects the resources IDs found in the SDK.
*
* @param classLoader The framework SDK jar classloader
* @return a map of the resources, or null if it failed.
*/
private Map<ResourceType, List<ResourceItem>> collectResourceIds(
AndroidJarLoader classLoader) {
try {
Class<?> r = classLoader.loadClass(AndroidConstants.CLASS_R);
if (r != null) {
return parseRClass(r);
}
} catch (ClassNotFoundException e) {
AdtPlugin.logAndPrintError(e, TAG,
"Collect resource IDs failed, class %1$s not found in %2$s", //$NON-NLS-1$
AndroidConstants.CLASS_R,
classLoader.getSource());
}
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,
classLoader.getSource());
}
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 osLibPath The OS path to the SDK tools/lib folder, ending with a separator.
* @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(String osLibPath,
ArrayList<String> activityActions, ArrayList<String> broadcastActions,
ArrayList<String> serviceActions, ArrayList<String> categories) {
collectValues(osLibPath + "activity_actions.txt" , activityActions);
collectValues(osLibPath + "broadcast_actions.txt" , broadcastActions);
collectValues(osLibPath + "service_actions.txt" , serviceActions);
collectValues(osLibPath + "categories.txt" , categories);
}
/**
* 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 osLibPath The OS path to the SDK tools/lib folder, ending with a separator.
* @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 collectLayoutClasses(String osLibPath,
AndroidJarLoader classLoader,
AttrsXmlParser attrsXmlParser,
Collection<ViewClassInfo> mainList, Collection<ViewClassInfo> groupList,
IProgressMonitor monitor) {
LayoutParamsParser ldp = null;
try {
WidgetListLoader loader = new WidgetListLoader(osLibPath + "widgets.txt");
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", //$NON-NLS-1$
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 manifest definition information from the attrs_manifest.xml and returns it.
*/
private Map<String, DeclareStyleableInfo> collectManifestDefinitions(
AttrsXmlParser attrsXmlParser) {
return attrsXmlParser.getDeclareStyleableList();
}
/**
* Returns the URL to the local documentation.
* Can return null if no documentation is found in the current SDK.
*
* @param osDocsPath Path to the documentation folder in the current SDK.
* The folder may not actually exist.
* @return A file:// URL on the local documentation folder if it exists or null.
*/
private String getDocumentationBaseUrl(String osDocsPath) {
File f = new File(osDocsPath);
if (f.isDirectory()) {
try {
// Note: to create a file:// URL, one would typically use something like
// f.toURI().toURL().toString(). However this generates a broken path on
// Windows, namely "C:\\foo" is converted to "file:/C:/foo" instead of
// "file:///C:/foo" (i.e. there should be 3 / after "file:"). So we'll
// do the correct thing manually.
String path = f.getAbsolutePath();
if (File.separatorChar != '/') {
path = path.replace(File.separatorChar, '/');
}
// For some reason the URL class doesn't add the mandatory "//" after
// the "file:" protocol name, so it has to be hacked into the path.
URL url = new URL("file", null, "//" + path); //$NON-NLS-1$ //$NON-NLS-2$
String result = url.toString();
return result;
} catch (MalformedURLException e) {
// ignore malformed URLs
}
}
return null;
}
}

View File

@@ -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.resources;
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 FrameworkResourceParser}.
*/
public 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,65 @@
/*
* 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.resources;
import com.android.ide.eclipse.adt.resources.LayoutParamsParser.IClass;
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 IAndroidLoader {
/**
* 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<IClass>> findClassesDerivingFrom(
String rootPackage, String[] superClasses)
throws IOException, InvalidAttributeValueException, ClassFormatError;
/**
* Returns a {@link IClass} by its fully-qualified name.
* @param className the fully-qualified name of the class to return.
* @throws ClassNotFoundException
*/
public IClass 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,380 @@
/*
* 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.resources;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.CommonPlugin;
import com.android.ide.eclipse.common.resources.AttrsXmlParser;
import com.android.ide.eclipse.common.resources.ViewClassInfo;
import com.android.ide.eclipse.common.resources.ViewClassInfo.LayoutParamsInfo;
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 {
/**
* Classes which implement this interface provide methods to describe a class.
*/
public interface IClass {
public String getCanonicalName();
public IClass getSuperclass();
public String getSimpleName();
public IClass getEnclosingClass();
public IClass[] getDeclaredClasses();
public boolean isInstantiable();
}
final static class ExtViewClassInfo extends ViewClassInfo {
private boolean mIsInstantiable;
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 IClass mTopViewClass;
/** Reference to android.view.ViewGroup */
protected IClass mTopGroupClass;
/** Reference to android.view.ViewGroup$LayoutParams */
protected IClass mTopLayoutParamsClass;
/** Input list of all classes deriving from android.view.View */
protected ArrayList<IClass> mViewList;
/** Input list of all classes deriving from android.view.ViewGroup */
protected ArrayList<IClass> 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 IAndroidLoader mClassLoader;
/**
* Instantiate a new LayoutParamsParser.
* @param classLoader The android.jar class loader
* @param attrsXmlParser The parser of the attrs.xml file
*/
public LayoutParamsParser(IAndroidLoader classLoader,
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 IClass, 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<IClass>> 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 (IClass groupChild : mGroupList) {
addGroup(groupChild);
progress.worked(1);
}
for (IClass viewChild : mViewList) {
if (viewChild != mTopGroupClass) {
addView(viewChild);
}
progress.worked(1);
}
} catch (ClassNotFoundException e) {
CommonPlugin.log(e, "Problem loading class %1$s or %2$s", //$NON-NLS-1$
rootClassName, groupClassName);
} catch (InvalidAttributeValueException e) {
CommonPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
} catch (ClassFormatError e) {
CommonPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
} catch (IOException e) {
CommonPlugin.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(IClass 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) {
IClass 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(IClass 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.
IClass 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(IClass groupClass) {
// Is there a LayoutParams in this group class?
IClass layoutParamsClass = findLayoutParams(groupClass);
// if there's no layout data in the group class, link to the one from the
// super class.
if (layoutParamsClass == null) {
for (IClass superClass = groupClass.getSuperclass();
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(IClass 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) {
IClass 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 IClass findLayoutParams(IClass groupClass) {
IClass[] innerClasses = groupClass.getDeclaredClasses();
for (IClass innerClass : innerClasses) {
if (innerClass.getSimpleName().equals(AndroidConstants.CLASS_LAYOUTPARAMS)) {
return innerClass;
}
}
return null;
}
private List<ViewClassInfo> getInstantiables(SortedMap<String, ExtViewClassInfo> map) {
Collection<ExtViewClassInfo> values = map.values();
ArrayList<ViewClassInfo> list = new ArrayList<ViewClassInfo>();
for (ExtViewClassInfo info : values) {
if (info.mIsInstantiable) {
list.add(info);
}
}
return list;
}
}

View File

@@ -0,0 +1,333 @@
/*
* 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.resources;
import com.android.ide.eclipse.adt.resources.LayoutParamsParser.IClass;
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 WidgetListLoader implements IAndroidLoader {
/**
* Basic class containing the class descriptions found in the text file.
*/
private final static class ClassDescriptor implements IClass {
private String mName;
private String mSimpleName;
private ClassDescriptor mSuperClass;
private ClassDescriptor mEnclosingClass;
private final ArrayList<IClass> mDeclaredClasses = new ArrayList<IClass>();
private boolean mIsInstantiable = false;
ClassDescriptor(String fqcn) {
mName = fqcn;
mSimpleName = getSimpleName(fqcn);
}
public String getCanonicalName() {
return mName;
}
public String getSimpleName() {
return mSimpleName;
}
public IClass[] getDeclaredClasses() {
return mDeclaredClasses.toArray(new IClass[mDeclaredClasses.size()]);
}
private void addDeclaredClass(ClassDescriptor declaredClass) {
mDeclaredClasses.add(declaredClass);
}
public IClass 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 IClass 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.
*/
WidgetListLoader(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<IClass>> findClassesDerivingFrom(String rootPackage,
String[] superClasses) throws IOException, InvalidAttributeValueException,
ClassFormatError {
HashMap<String, ArrayList<IClass>> map = new HashMap<String, ArrayList<IClass>>();
ArrayList<IClass> list = new ArrayList<IClass>();
list.addAll(mWidgetMap.values());
map.put(AndroidConstants.CLASS_VIEW, list);
list = new ArrayList<IClass>();
list.addAll(mLayoutMap.values());
map.put(AndroidConstants.CLASS_VIEWGROUP, list);
list = new ArrayList<IClass>();
list.addAll(mLayoutParamsMap.values());
map.put(AndroidConstants.CLASS_VIEWGROUP_LAYOUTPARAMS, list);
return map;
}
/**
* Returns a {@link IClass} by its fully-qualified name.
* @param className the fully-qualified name of the class to return.
* @throws ClassNotFoundException
*/
public IClass getClass(String className) throws ClassNotFoundException {
return mMap.get(className);
}
}

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="PACKAGE"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="APPLICATION_NAME">
ACTIVITIES
</application>
</manifest>

View File

@@ -0,0 +1,7 @@
<activity android:name=".ACTIVITY_NAME"
android:label="APPLICATION_NAME">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
INTENT_FILTERS
</intent-filter>
</activity>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,13 @@
package PACKAGE;
import android.app.Activity;
import android.os.Bundle;
public class ACTIVITY_NAME extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}

View File

@@ -0,0 +1 @@
<category android:name="android.intent.category.LAUNCHER" />

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
</LinearLayout>

View File

@@ -0,0 +1 @@
<category android:name="android.intent.category.PREFERENCE" />

View File

@@ -0,0 +1 @@
<string name="STRING_NAME">STRING_CONTENT</string>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
STRINGS
</resources>