Code drop from //branches/cupcake/...@124589
@@ -5,5 +5,12 @@
|
||||
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
|
||||
<classpathentry kind="lib" path="jarutils.jar"/>
|
||||
<classpathentry kind="lib" path="androidprefs.jar"/>
|
||||
<classpathentry kind="lib" path="sdkstats.jar"/>
|
||||
<classpathentry kind="lib" path="kxml2-2.3.0.jar"/>
|
||||
<classpathentry kind="lib" path="layoutlib_api.jar"/>
|
||||
<classpathentry kind="lib" path="layoutlib_utils.jar"/>
|
||||
<classpathentry kind="lib" path="ninepatch.jar"/>
|
||||
<classpathentry kind="lib" path="sdklib.jar"/>
|
||||
<classpathentry kind="lib" path="sdkuilib.jar"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
|
||||
@@ -2,14 +2,20 @@ Manifest-Version: 1.0
|
||||
Bundle-ManifestVersion: 2
|
||||
Bundle-Name: Android Development Toolkit
|
||||
Bundle-SymbolicName: com.android.ide.eclipse.adt;singleton:=true
|
||||
Bundle-Version: 0.8.1.qualifier
|
||||
Bundle-Version: 0.9.0.qualifier
|
||||
Bundle-ClassPath: .,
|
||||
jarutils.jar,
|
||||
androidprefs.jar
|
||||
androidprefs.jar,
|
||||
sdkstats.jar,
|
||||
kxml2-2.3.0.jar,
|
||||
layoutlib_api.jar,
|
||||
ninepatch.jar,
|
||||
layoutlib_utils.jar,
|
||||
sdklib.jar,
|
||||
sdkuilib.jar
|
||||
Bundle-Activator: com.android.ide.eclipse.adt.AdtPlugin
|
||||
Bundle-Vendor: The Android Open Source Project
|
||||
Require-Bundle: com.android.ide.eclipse.common,
|
||||
com.android.ide.eclipse.ddms,
|
||||
Require-Bundle: com.android.ide.eclipse.ddms,
|
||||
org.eclipse.core.runtime,
|
||||
org.eclipse.core.resources,
|
||||
org.eclipse.debug.core,
|
||||
@@ -26,11 +32,48 @@ Require-Bundle: com.android.ide.eclipse.common,
|
||||
org.eclipse.core.filesystem,
|
||||
org.eclipse.ui,
|
||||
org.eclipse.ui.ide,
|
||||
org.eclipse.ui.forms
|
||||
org.eclipse.ui.forms,
|
||||
org.eclipse.gef,
|
||||
org.eclipse.ui.browser,
|
||||
org.eclipse.ui.views,
|
||||
org.eclipse.wst.sse.core,
|
||||
org.eclipse.wst.sse.ui,
|
||||
org.eclipse.wst.xml.core,
|
||||
org.eclipse.wst.xml.ui
|
||||
Eclipse-LazyStart: true
|
||||
Export-Package: com.android.ide.eclipse.adt.build;x-friends:="com.android.ide.eclipse.tests",
|
||||
Export-Package: com.android.ide.eclipse.adt,
|
||||
com.android.ide.eclipse.adt.build;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.adt.project;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.adt.project.internal;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.adt.resources;x-friends:="com.android.ide.eclipse.tests"
|
||||
com.android.ide.eclipse.adt.sdk;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.adt.wizards.newproject;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.common,
|
||||
com.android.ide.eclipse.common.project,
|
||||
com.android.ide.eclipse.common.resources,
|
||||
com.android.ide.eclipse.editors;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.descriptors;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.layout;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.layout.descriptors;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.layout.parts;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.layout.uimodel;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.manifest;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.manifest.descriptors;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.manifest.model;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.manifest.pages;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.menu;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.menu.descriptors;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.resources;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.resources.configurations;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.resources.descriptors;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.resources.explorer;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.resources.manager;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.resources.manager.files;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.resources.uimodel;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.ui;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.ui.tree;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.uimodel;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.wizards;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.xml;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.editors.xml.descriptors;x-friends:="com.android.ide.eclipse.tests"
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,13 @@ bin.includes = plugin.xml,\
|
||||
templates/,\
|
||||
about.ini,\
|
||||
jarutils.jar,\
|
||||
androidprefs.jar
|
||||
androidprefs.jar,\
|
||||
sdkstats.jar,\
|
||||
kxml2-2.3.0.jar,\
|
||||
layoutlib_api.jar,\
|
||||
layoutlib_utils.jar,\
|
||||
ninepatch.jar,\
|
||||
sdklib.jar,\
|
||||
sdkuilib.jar
|
||||
source.. = src/
|
||||
output.. = bin/
|
||||
|
||||
BIN
tools/eclipse/plugins/com.android.ide.eclipse.adt/icons/add.png
Normal file
|
After Width: | Height: | Size: 146 B |
|
After Width: | Height: | Size: 363 B |
|
After Width: | Height: | Size: 107 B |
|
After Width: | Height: | Size: 320 B |
BIN
tools/eclipse/plugins/com.android.ide.eclipse.adt/icons/down.png
Normal file
|
After Width: | Height: | Size: 157 B |
BIN
tools/eclipse/plugins/com.android.ide.eclipse.adt/icons/dpi.png
Normal file
|
After Width: | Height: | Size: 302 B |
|
After Width: | Height: | Size: 194 B |
|
After Width: | Height: | Size: 307 B |
|
After Width: | Height: | Size: 287 B |
|
After Width: | Height: | Size: 138 B |
BIN
tools/eclipse/plugins/com.android.ide.eclipse.adt/icons/mcc.png
Normal file
|
After Width: | Height: | Size: 463 B |
BIN
tools/eclipse/plugins/com.android.ide.eclipse.adt/icons/mnc.png
Normal file
|
After Width: | Height: | Size: 265 B |
|
After Width: | Height: | Size: 308 B |
|
After Width: | Height: | Size: 325 B |
|
After Width: | Height: | Size: 445 B |
|
After Width: | Height: | Size: 321 B |
|
After Width: | Height: | Size: 344 B |
BIN
tools/eclipse/plugins/com.android.ide.eclipse.adt/icons/up.png
Normal file
|
After Width: | Height: | Size: 137 B |
|
After Width: | Height: | Size: 147 B |
@@ -1,6 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?eclipse version="3.2"?>
|
||||
<plugin>
|
||||
<extension
|
||||
id="com.android.ide.eclipse.common.xmlProblem"
|
||||
name="Android XML Format Problem"
|
||||
point="org.eclipse.core.resources.markers">
|
||||
<super type="org.eclipse.core.resources.problemmarker"/>
|
||||
<super type="org.eclipse.core.resources.textmarker"/>
|
||||
<persistent value="true"/>
|
||||
</extension>
|
||||
<extension
|
||||
id="com.android.ide.eclipse.common.aaptProblem"
|
||||
name="Android AAPT Problem"
|
||||
point="org.eclipse.core.resources.markers">
|
||||
<super type="org.eclipse.core.resources.problemmarker"/>
|
||||
<super type="org.eclipse.core.resources.textmarker"/>
|
||||
<persistent value="true"/>
|
||||
</extension>
|
||||
<extension
|
||||
id="com.android.ide.eclipse.common.aidlProblem"
|
||||
name="Android AIDL Problem"
|
||||
point="org.eclipse.core.resources.markers">
|
||||
<super type="org.eclipse.core.resources.problemmarker"/>
|
||||
<super type="org.eclipse.core.resources.textmarker"/>
|
||||
<persistent value="true"/>
|
||||
</extension>
|
||||
<extension
|
||||
id="com.android.ide.eclipse.common.androidProblem"
|
||||
name="Android XML Content Problem"
|
||||
point="org.eclipse.core.resources.markers">
|
||||
<super type="org.eclipse.core.resources.problemmarker"/>
|
||||
<super type="org.eclipse.core.resources.textmarker"/>
|
||||
<persistent value="true"/>
|
||||
</extension>
|
||||
<extension
|
||||
id="ResourceManagerBuilder"
|
||||
name="Android Resource Manager"
|
||||
@@ -47,7 +79,7 @@
|
||||
<wizard
|
||||
canFinishEarly="false"
|
||||
category="com.android.ide.eclipse.wizards.category"
|
||||
class="com.android.ide.eclipse.adt.project.internal.NewProjectWizard"
|
||||
class="com.android.ide.eclipse.adt.wizards.newproject.NewProjectWizard"
|
||||
finalPerspective="org.eclipse.jdt.ui.JavaPerspective"
|
||||
hasPages="true"
|
||||
icon="icons/android.png"
|
||||
@@ -55,6 +87,18 @@
|
||||
name="Android Project"
|
||||
preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective"
|
||||
project="true"/>
|
||||
<wizard
|
||||
canFinishEarly="false"
|
||||
category="com.android.ide.eclipse.wizards.category"
|
||||
class="com.android.ide.eclipse.editors.wizards.NewXmlFileWizard"
|
||||
finalPerspective="org.eclipse.jdt.ui.JavaPerspective"
|
||||
hasPages="true"
|
||||
icon="icons/android.png"
|
||||
id="com.android.ide.eclipse.editors.wizards.NewXmlFileWizard"
|
||||
name="Android XML File"
|
||||
preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective"
|
||||
project="false">
|
||||
</wizard>
|
||||
</extension>
|
||||
<extension
|
||||
point="org.eclipse.debug.core.launchConfigurationTypes">
|
||||
@@ -173,6 +217,13 @@
|
||||
id="com.android.ide.eclipse.adt.project.CreateAidlImportAction"
|
||||
label="Create Aidl preprocess file for Parcelable classes"
|
||||
menubarPath="com.android.ide.eclipse.adt.AndroidTools/group1"/>
|
||||
<action
|
||||
class="com.android.ide.eclipse.adt.project.NewXmlFileWizardAction"
|
||||
enablesFor="1"
|
||||
id="com.android.ide.eclipse.adt.project.NewXmlFileWizardAction"
|
||||
label="New Resource File..."
|
||||
menubarPath="com.android.ide.eclipse.adt.AndroidTools/group1">
|
||||
</action>
|
||||
<action
|
||||
class="com.android.ide.eclipse.adt.project.ExportAction"
|
||||
enablesFor="1"
|
||||
@@ -183,7 +234,7 @@
|
||||
class="com.android.ide.eclipse.adt.project.ExportWizardAction"
|
||||
enablesFor="1"
|
||||
id="com.android.ide.eclipse.adt.project.ExportWizardAction"
|
||||
label="Export Application..."
|
||||
label="Export Signed Application Package..."
|
||||
menubarPath="com.android.ide.eclipse.adt.AndroidTools/group2"/>
|
||||
<action
|
||||
class="com.android.ide.eclipse.adt.project.FixProjectAction"
|
||||
@@ -209,29 +260,31 @@
|
||||
class="com.android.ide.eclipse.adt.preferences.LaunchPreferencePage"
|
||||
id="com.android.ide.eclipse.adt.preferences.LaunchPreferencePage"
|
||||
name="Launch"/>
|
||||
<page
|
||||
category="com.android.ide.eclipse.preferences.main"
|
||||
class="com.android.ide.eclipse.common.preferences.UsagePreferencePage"
|
||||
id="com.android.ide.eclipse.common.preferences.UsagePreferencePage"
|
||||
name="Usage Stats">
|
||||
</page>
|
||||
</extension>
|
||||
<extension
|
||||
point="org.eclipse.core.runtime.preferences">
|
||||
<initializer class="com.android.ide.eclipse.adt.preferences.PreferenceInitializer"/>
|
||||
</extension>
|
||||
<extension
|
||||
point="org.eclipse.ui.editors">
|
||||
<editor
|
||||
class="com.android.ide.eclipse.adt.editors.java.ReadOnlyJavaEditor"
|
||||
contributorClass="org.eclipse.ui.texteditor.BasicTextEditorActionContributor"
|
||||
default="true"
|
||||
filenames="R.java, Manifest.java"
|
||||
icon="icons/android.png"
|
||||
id="com.android.ide.eclipse.adt.editors.java.ReadOnlyJavaEditor"
|
||||
name="Android Java Editor"/>
|
||||
</extension>
|
||||
<extension
|
||||
id="com.android.ide.eclipse.adt.adtProblem"
|
||||
name="Generic ADT Problem"
|
||||
name="Android ADT Problem"
|
||||
point="org.eclipse.core.resources.markers">
|
||||
<super type="org.eclipse.core.resources.problemmarker"/>
|
||||
<persistent value="true"/>
|
||||
</extension>
|
||||
<extension
|
||||
id="com.android.ide.eclipse.adt.targetProblem"
|
||||
name="Android Target Problem"
|
||||
point="org.eclipse.core.resources.markers">
|
||||
<super type="org.eclipse.core.resources.problemmarker"/>
|
||||
<persistent value="false"/>
|
||||
</extension>
|
||||
<extension
|
||||
point="org.eclipse.ui.perspectiveExtensions">
|
||||
<perspectiveExtension targetID="org.eclipse.jdt.ui.JavaPerspective">
|
||||
@@ -305,4 +358,110 @@
|
||||
keyConfigurationId="org.eclipse.ui.defaultAcceleratorConfiguration">
|
||||
</keyBinding>
|
||||
</extension>
|
||||
<extension
|
||||
point="org.eclipse.ui.decorators">
|
||||
<decorator
|
||||
adaptable="true"
|
||||
class="com.android.ide.eclipse.adt.project.FolderDecorator"
|
||||
id="com.android.ide.eclipse.adt.project.FolderDecorator"
|
||||
label="Android Decorator"
|
||||
lightweight="true"
|
||||
location="TOP_RIGHT"
|
||||
objectClass="org.eclipse.core.resources.IFolder"
|
||||
state="true">
|
||||
</decorator>
|
||||
</extension>
|
||||
<extension
|
||||
point="org.eclipse.ui.editors">
|
||||
<editor
|
||||
class="com.android.ide.eclipse.editors.manifest.ManifestEditor"
|
||||
default="true"
|
||||
filenames="AndroidManifest.xml"
|
||||
icon="icons/android.png"
|
||||
id="com.android.ide.eclipse.editors.manifest.ManifestEditor"
|
||||
name="Android Manifest Editor">
|
||||
</editor>
|
||||
<editor
|
||||
class="com.android.ide.eclipse.editors.resources.ResourcesEditor"
|
||||
default="false"
|
||||
extensions="xml"
|
||||
icon="icons/android.png"
|
||||
id="com.android.ide.eclipse.editors.resources.ResourcesEditor"
|
||||
name="Android Resource Editor">
|
||||
</editor>
|
||||
<editor
|
||||
class="com.android.ide.eclipse.editors.layout.LayoutEditor"
|
||||
default="false"
|
||||
extensions="xml"
|
||||
icon="icons/android.png"
|
||||
id="com.android.ide.eclipse.editors.layout.LayoutEditor"
|
||||
matchingStrategy="com.android.ide.eclipse.editors.layout.MatchingStrategy"
|
||||
name="Android Layout Editor">
|
||||
</editor>
|
||||
<editor
|
||||
class="com.android.ide.eclipse.editors.menu.MenuEditor"
|
||||
default="false"
|
||||
extensions="xml"
|
||||
icon="icons/android.png"
|
||||
id="com.android.ide.eclipse.editors.menu.MenuEditor"
|
||||
name="Android Menu Editor">
|
||||
</editor>
|
||||
<editor
|
||||
class="com.android.ide.eclipse.editors.xml.XmlEditor"
|
||||
default="false"
|
||||
extensions="xml"
|
||||
icon="icons/android.png"
|
||||
id="com.android.ide.eclipse.editors.xml.XmlEditor"
|
||||
name="Android Xml Resources Editor">
|
||||
</editor>
|
||||
</extension>
|
||||
<extension
|
||||
point="org.eclipse.ui.views">
|
||||
<view
|
||||
allowMultiple="false"
|
||||
category="com.android.ide.eclipse.ddms.views.category"
|
||||
class="com.android.ide.eclipse.editors.resources.explorer.ResourceExplorerView"
|
||||
icon="icons/android.png"
|
||||
id="com.android.ide.eclipse.editors.resources.explorer.ResourceExplorerView"
|
||||
name="Resource Explorer">
|
||||
</view>
|
||||
</extension>
|
||||
<extension
|
||||
point="org.eclipse.wst.sse.ui.editorConfiguration">
|
||||
<sourceViewerConfiguration
|
||||
class="com.android.ide.eclipse.editors.manifest.ManifestSourceViewerConfig"
|
||||
target="com.android.ide.eclipse.editors.manifest.ManifestEditor">
|
||||
</sourceViewerConfiguration>
|
||||
<sourceViewerConfiguration
|
||||
class="com.android.ide.eclipse.editors.resources.ResourcesSourceViewerConfig"
|
||||
target="com.android.ide.eclipse.editors.resources.ResourcesEditor">
|
||||
</sourceViewerConfiguration>
|
||||
<sourceViewerConfiguration
|
||||
class="com.android.ide.eclipse.editors.layout.LayoutSourceViewerConfig"
|
||||
target="com.android.ide.eclipse.editors.layout.LayoutEditor">
|
||||
</sourceViewerConfiguration>
|
||||
<sourceViewerConfiguration
|
||||
class="com.android.ide.eclipse.editors.menu.MenuSourceViewerConfig"
|
||||
target="com.android.ide.eclipse.editors.menu.MenuEditor">
|
||||
</sourceViewerConfiguration>
|
||||
<sourceViewerConfiguration
|
||||
class="com.android.ide.eclipse.editors.xml.XmlSourceViewerConfig"
|
||||
target="com.android.ide.eclipse.editors.xml.XmlEditor">
|
||||
</sourceViewerConfiguration>
|
||||
</extension>
|
||||
<extension
|
||||
point="org.eclipse.ui.propertyPages">
|
||||
<page
|
||||
adaptable="true"
|
||||
class="com.android.ide.eclipse.adt.project.properties.AndroidPropertyPage"
|
||||
id="com.android.ide.eclipse.adt.project.properties.AndroidPropertyPage"
|
||||
name="Android"
|
||||
nameFilter="*"
|
||||
objectClass="org.eclipse.core.resources.IProject">
|
||||
<enabledWhen>
|
||||
<test property="org.eclipse.jdt.launching.hasProjectNature"
|
||||
args="com.android.ide.eclipse.adt.AndroidNature"/>
|
||||
</enabledWhen>
|
||||
</page>
|
||||
</extension>
|
||||
</plugin>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.ide.eclipse.adt;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constant definition class.<br>
|
||||
* <br>
|
||||
@@ -39,6 +40,12 @@ public class AdtConstants {
|
||||
/** Generic marker for ADT errors. */
|
||||
public final static String MARKER_ADT = AdtPlugin.PLUGIN_ID + ".adtProblem"; //$NON-NLS-1$
|
||||
|
||||
/** Marker for Android Target errors.
|
||||
* This is not cleared on each like other markers. Instead, it's cleared
|
||||
* when a ContainerClasspathInitialized has succeeded in creating an
|
||||
* {@link AndroidClasspathContainer}*/
|
||||
public final static String MARKER_TARGET = AdtPlugin.PLUGIN_ID + ".targetProblem"; //$NON-NLS-1$
|
||||
|
||||
/** Build verbosity "Always". Those messages are always displayed. */
|
||||
public final static int BUILD_ALWAYS = 0;
|
||||
|
||||
|
||||
@@ -22,34 +22,55 @@ import com.android.ddmuilib.console.DdmConsole;
|
||||
import com.android.ddmuilib.console.IDdmConsole;
|
||||
import com.android.ide.eclipse.adt.build.DexWrapper;
|
||||
import com.android.ide.eclipse.adt.debug.launching.AndroidLaunchController;
|
||||
import com.android.ide.eclipse.adt.debug.ui.SkinRepository;
|
||||
import com.android.ide.eclipse.adt.preferences.BuildPreferencePage;
|
||||
import com.android.ide.eclipse.adt.project.ProjectHelper;
|
||||
import com.android.ide.eclipse.adt.project.export.ExportWizard;
|
||||
import com.android.ide.eclipse.adt.project.internal.AndroidClasspathContainerInitializer;
|
||||
import com.android.ide.eclipse.adt.resources.FrameworkResourceParser;
|
||||
import com.android.ide.eclipse.adt.sdk.AndroidTargetParser;
|
||||
import com.android.ide.eclipse.adt.sdk.LoadStatus;
|
||||
import com.android.ide.eclipse.adt.sdk.Sdk;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.common.CommonPlugin;
|
||||
import com.android.ide.eclipse.common.EclipseUiHelper;
|
||||
import com.android.ide.eclipse.common.SdkStatsHelper;
|
||||
import com.android.ide.eclipse.common.StreamHelper;
|
||||
import com.android.ide.eclipse.common.project.BaseProjectHelper;
|
||||
import com.android.ide.eclipse.common.project.ExportHelper;
|
||||
import com.android.ide.eclipse.common.project.ExportHelper.IExportCallback;
|
||||
import com.android.ide.eclipse.common.resources.FrameworkResourceManager;
|
||||
import com.android.ide.eclipse.ddms.DdmsPlugin;
|
||||
import com.android.ide.eclipse.ddms.ImageLoader;
|
||||
import com.android.ide.eclipse.editors.IconFactory;
|
||||
import com.android.ide.eclipse.editors.layout.LayoutEditor;
|
||||
import com.android.ide.eclipse.editors.menu.MenuEditor;
|
||||
import com.android.ide.eclipse.editors.resources.ResourcesEditor;
|
||||
import com.android.ide.eclipse.editors.resources.manager.ProjectResources;
|
||||
import com.android.ide.eclipse.editors.resources.manager.ResourceFolder;
|
||||
import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType;
|
||||
import com.android.ide.eclipse.editors.resources.manager.ResourceManager;
|
||||
import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor;
|
||||
import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFileListener;
|
||||
import com.android.ide.eclipse.editors.xml.XmlEditor;
|
||||
import com.android.sdklib.IAndroidTarget;
|
||||
import com.android.sdklib.SdkConstants;
|
||||
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.resources.IFolder;
|
||||
import org.eclipse.core.resources.IMarkerDelta;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IResourceDelta;
|
||||
import org.eclipse.core.resources.IWorkspace;
|
||||
import org.eclipse.core.resources.ResourcesPlugin;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.Platform;
|
||||
import org.eclipse.core.runtime.Preferences;
|
||||
import org.eclipse.core.runtime.QualifiedName;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.eclipse.core.runtime.SubMonitor;
|
||||
import org.eclipse.core.runtime.Preferences.IPropertyChangeListener;
|
||||
import org.eclipse.core.runtime.Preferences.PropertyChangeEvent;
|
||||
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
|
||||
import org.eclipse.core.runtime.jobs.Job;
|
||||
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jface.dialogs.MessageDialog;
|
||||
import org.eclipse.jface.preference.IPreferenceStore;
|
||||
@@ -57,14 +78,21 @@ import org.eclipse.jface.resource.ImageDescriptor;
|
||||
import org.eclipse.jface.viewers.StructuredSelection;
|
||||
import org.eclipse.jface.wizard.WizardDialog;
|
||||
import org.eclipse.swt.graphics.Color;
|
||||
import org.eclipse.swt.graphics.Image;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
import org.eclipse.swt.widgets.Shell;
|
||||
import org.eclipse.ui.IEditorDescriptor;
|
||||
import org.eclipse.ui.IEditorPart;
|
||||
import org.eclipse.ui.IWorkbench;
|
||||
import org.eclipse.ui.IWorkbenchPage;
|
||||
import org.eclipse.ui.PlatformUI;
|
||||
import org.eclipse.ui.console.ConsolePlugin;
|
||||
import org.eclipse.ui.console.IConsole;
|
||||
import org.eclipse.ui.console.IConsoleConstants;
|
||||
import org.eclipse.ui.console.MessageConsole;
|
||||
import org.eclipse.ui.console.MessageConsoleStream;
|
||||
import org.eclipse.ui.ide.IDE;
|
||||
import org.eclipse.ui.part.FileEditorInput;
|
||||
import org.eclipse.ui.plugin.AbstractUIPlugin;
|
||||
import org.osgi.framework.Bundle;
|
||||
import org.osgi.framework.BundleContext;
|
||||
@@ -81,6 +109,8 @@ import java.io.PrintStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The activator class controls the plug-in life cycle
|
||||
@@ -106,14 +136,17 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
/** singleton instance */
|
||||
private static AdtPlugin sPlugin;
|
||||
|
||||
private static Image sAndroidLogo;
|
||||
private static ImageDescriptor sAndroidLogoDesc;
|
||||
|
||||
/** default store, provided by eclipse */
|
||||
private IPreferenceStore mStore;
|
||||
|
||||
/** cached location for the sdk folder */
|
||||
private String mOsSdkLocation;
|
||||
|
||||
/** SDK Api Version */
|
||||
String mSdkApiVersion;
|
||||
/** The global android console */
|
||||
private MessageConsole mAndroidConsole;
|
||||
|
||||
/** Stream to write in the android console */
|
||||
private MessageConsoleStream mAndroidConsoleStream;
|
||||
@@ -130,14 +163,14 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
/** Color used in the error console */
|
||||
private Color mRed;
|
||||
|
||||
private final ArrayList<IJavaProject> mPostDexProjects = new ArrayList<IJavaProject>();
|
||||
/** Load status of the SDK. Any access MUST be in a synchronized(mPostLoadProjects) block */
|
||||
private LoadStatus mSdkIsLoaded = LoadStatus.LOADING;
|
||||
/** Project to update once the SDK is loaded.
|
||||
* Any access MUST be in a synchronized(mPostLoadProjects) block */
|
||||
private final ArrayList<IJavaProject> mPostLoadProjects = new ArrayList<IJavaProject>();
|
||||
|
||||
/** Boolean wrapper to run dialog in the UI thread, and still get the
|
||||
* return code.
|
||||
*/
|
||||
private static final class BooleanWrapper {
|
||||
public boolean b;
|
||||
}
|
||||
private ResourceMonitor mResourceMonitor;
|
||||
private ArrayList<Runnable> mResourceRefreshListener = new ArrayList<Runnable>();
|
||||
|
||||
/**
|
||||
* Custom PrintStream for Dx output. This class overrides the method
|
||||
@@ -210,10 +243,14 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
|
||||
Display display = getDisplay();
|
||||
|
||||
// set the default android console.
|
||||
mAndroidConsole = new MessageConsole("Android", null); //$NON-NLS-1$
|
||||
ConsolePlugin.getDefault().getConsoleManager().addConsoles(
|
||||
new IConsole[] { mAndroidConsole });
|
||||
|
||||
// get the stream to write in the android console.
|
||||
MessageConsole androidConsole = CommonPlugin.getDefault().getAndroidConsole();
|
||||
mAndroidConsoleStream = androidConsole.newMessageStream();
|
||||
mAndroidConsoleErrorStream = androidConsole.newMessageStream();
|
||||
mAndroidConsoleStream = mAndroidConsole.newMessageStream();
|
||||
mAndroidConsoleErrorStream = mAndroidConsole.newMessageStream();
|
||||
mRed = new Color(display, 0xFF, 0x00, 0x00);
|
||||
|
||||
// because this can be run, in some cases, by a non ui thread, and beccause
|
||||
@@ -267,22 +304,19 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
|
||||
// get the SDK location and build id.
|
||||
if (checkSdkLocationAndId()) {
|
||||
// if sdk if valid, reparse the skin folder
|
||||
SkinRepository.getInstance().parseFolder(getOsSkinFolder());
|
||||
// if sdk if valid, reparse it
|
||||
|
||||
// add the current Android project to the list of projects to be updated
|
||||
// after the SDK is reloaded
|
||||
synchronized (mPostLoadProjects) {
|
||||
// get the project to refresh.
|
||||
IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects();
|
||||
mPostLoadProjects.addAll(Arrays.asList(androidProjects));
|
||||
}
|
||||
|
||||
// parse the SDK resources at the new location
|
||||
parseSdkContent();
|
||||
}
|
||||
|
||||
// parse the SDK resources at the new location
|
||||
parseSdkContent();
|
||||
|
||||
// get the project to refresh.
|
||||
IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects();
|
||||
|
||||
// Setup the new container for each project. By providing new instances of
|
||||
// AndroidClasspathContainer, this will force JDT to call
|
||||
// IClasspathContainer#getClasspathEntries() again and receive the new
|
||||
// path to the framework jar.
|
||||
AndroidClasspathContainerInitializer.updateProjects(androidProjects);
|
||||
|
||||
} else if (PREFS_BUILD_VERBOSITY.equals(property)) {
|
||||
mBuildVerbosity = BuildPreferencePage.getBuildLevel(
|
||||
mStore.getString(PREFS_BUILD_VERBOSITY));
|
||||
@@ -299,20 +333,11 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
}
|
||||
|
||||
// check the location of SDK
|
||||
if (checkSdkLocationAndId()) {
|
||||
// if sdk if valid, parse the skin folder
|
||||
SkinRepository.getInstance().parseFolder(getOsSkinFolder());
|
||||
|
||||
// parse the SDK resources.
|
||||
parseSdkContent();
|
||||
}
|
||||
final boolean isSdkLocationValid = checkSdkLocationAndId();
|
||||
|
||||
mBuildVerbosity = BuildPreferencePage.getBuildLevel(
|
||||
mStore.getString(PREFS_BUILD_VERBOSITY));
|
||||
|
||||
// Ping the usage start server.
|
||||
pingUsageServer();
|
||||
|
||||
// create the loader that's able to load the images
|
||||
mLoader = new ImageLoader(this);
|
||||
|
||||
@@ -356,18 +381,31 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
dialog.open();
|
||||
}
|
||||
});
|
||||
|
||||
// initialize editors
|
||||
startEditors();
|
||||
|
||||
/* The Editors plugin must be started as soon as Android projects are opened or created,
|
||||
* in order to properly set default editors on the layout/values XML files.
|
||||
*
|
||||
* This ensures that the default editors is really only set when a new XML file
|
||||
* is added to the workspace (IResourceDelta.ADDED event), through project creation or
|
||||
* manual add.
|
||||
* Other methods would force to go through existing projects when the Editors plugin is
|
||||
* started, and set the default editors for their XML files, possibly erasing user set
|
||||
* default editors.
|
||||
*/
|
||||
startEditorsPlugin();
|
||||
// Ping the usage server and parse the SDK content.
|
||||
// This is deferred in separate jobs to avoid blocking the bundle start.
|
||||
// We also serialize them to avoid too many parallel jobs when Eclipse starts.
|
||||
Job pingJob = createPingUsageServerJob();
|
||||
pingJob.addJobChangeListener(new JobChangeAdapter() {
|
||||
@Override
|
||||
public void done(IJobChangeEvent event) {
|
||||
super.done(event);
|
||||
|
||||
// Once the ping job is finished, start the SDK parser
|
||||
if (isSdkLocationValid) {
|
||||
// parse the SDK resources.
|
||||
parseSdkContent();
|
||||
}
|
||||
}
|
||||
});
|
||||
// build jobs are run after other interactive jobs
|
||||
pingJob.setPriority(Job.BUILD);
|
||||
// Wait 2 seconds before starting the ping job. This leaves some time to the
|
||||
// other bundles to initialize.
|
||||
pingJob.schedule(2000 /*milliseconds*/);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -379,6 +417,8 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
public void stop(BundleContext context) throws Exception {
|
||||
super.stop(context);
|
||||
|
||||
stopEditors();
|
||||
|
||||
DexWrapper.unloadDex();
|
||||
|
||||
mRed.dispose();
|
||||
@@ -418,37 +458,22 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
|
||||
/** Returns the adb path relative to the sdk folder */
|
||||
public static String getOsRelativeAdb() {
|
||||
return AndroidConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_ADB;
|
||||
return SdkConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_ADB;
|
||||
}
|
||||
|
||||
/** Returns the aapt path relative to the sdk folder */
|
||||
public static String getOsRelativeAapt() {
|
||||
return AndroidConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_AAPT;
|
||||
return SdkConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_AAPT;
|
||||
}
|
||||
|
||||
/** Returns the emulator path relative to the sdk folder */
|
||||
public static String getOsRelativeEmulator() {
|
||||
return AndroidConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_EMULATOR;
|
||||
return SdkConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_EMULATOR;
|
||||
}
|
||||
|
||||
/** Returns the aidl path relative to the sdk folder */
|
||||
public static String getOsRelativeAidl() {
|
||||
return AndroidConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_AIDL;
|
||||
}
|
||||
|
||||
/** Returns the framework jar path relative to the sdk folder */
|
||||
public static String getOsRelativeFramework() {
|
||||
return AndroidConstants.FN_FRAMEWORK_LIBRARY;
|
||||
}
|
||||
|
||||
/** Returns the android sources path relative to the sdk folder */
|
||||
public static String getOsRelativeAndroidSources() {
|
||||
return AndroidConstants.FD_ANDROID_SOURCES;
|
||||
}
|
||||
|
||||
/** Returns the framework jar path relative to the sdk folder */
|
||||
public static String getOsRelativeAttrsXml() {
|
||||
return AndroidConstants.OS_SDK_LIBS_FOLDER + AndroidConstants.FN_ATTRS_XML;
|
||||
return SdkConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_AIDL;
|
||||
}
|
||||
|
||||
/** Returns the absolute adb path */
|
||||
@@ -458,7 +483,7 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
|
||||
/** Returns the absolute traceview path */
|
||||
public static String getOsAbsoluteTraceview() {
|
||||
return getOsSdkFolder() + AndroidConstants.OS_SDK_TOOLS_FOLDER +
|
||||
return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER +
|
||||
AndroidConstants.FN_TRACEVIEW;
|
||||
}
|
||||
|
||||
@@ -467,21 +492,6 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
return getOsSdkFolder() + getOsRelativeAapt();
|
||||
}
|
||||
|
||||
/** Returns the absolute sdk framework path */
|
||||
public static String getOsAbsoluteFramework() {
|
||||
return getOsSdkFolder() + getOsRelativeFramework();
|
||||
}
|
||||
|
||||
/** Returns the absolute android sources path in the sdk */
|
||||
public static String getOsAbsoluteAndroidSources() {
|
||||
return getOsSdkFolder() + getOsRelativeAndroidSources();
|
||||
}
|
||||
|
||||
/** Returns the absolute attrs.xml path */
|
||||
public static String getOsAbsoluteAttrsXml() {
|
||||
return getOsSdkFolder() + getOsRelativeAttrsXml();
|
||||
}
|
||||
|
||||
/** Returns the absolute emulator path */
|
||||
public static String getOsAbsoluteEmulator() {
|
||||
return getOsSdkFolder() + getOsRelativeEmulator();
|
||||
@@ -492,16 +502,6 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
return getOsSdkFolder() + getOsRelativeAidl();
|
||||
}
|
||||
|
||||
/** Returns the absolute path to the aidl framework import file. */
|
||||
public static String getOsAbsoluteFrameworkAidl() {
|
||||
return getOsSdkFolder() + AndroidConstants.OS_SDK_LIBS_FOLDER +
|
||||
AndroidConstants.FN_FRAMEWORK_AIDL;
|
||||
}
|
||||
|
||||
public static String getOsSdkSamplesFolder() {
|
||||
return getOsSdkFolder() + AndroidConstants.OS_SDK_SAMPLES_FOLDER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Url file path to the javaDoc folder.
|
||||
*/
|
||||
@@ -526,11 +526,7 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
}
|
||||
|
||||
public static String getOsSdkToolsFolder() {
|
||||
return getOsSdkFolder() + AndroidConstants.OS_SDK_TOOLS_FOLDER;
|
||||
}
|
||||
|
||||
public static String getOsSkinFolder() {
|
||||
return getOsSdkFolder() + AndroidConstants.OS_SDK_SKINS_FOLDER;
|
||||
return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER;
|
||||
}
|
||||
|
||||
public static synchronized boolean getAutoResRefresh() {
|
||||
@@ -540,18 +536,6 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
return sPlugin.mStore.getBoolean(PREFS_RES_AUTO_REFRESH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SDK build id.
|
||||
* @return a string containing the SDK build id, or null it it is unknownn.
|
||||
*/
|
||||
public static synchronized String getSdkApiVersion() {
|
||||
if (sPlugin != null) {
|
||||
return sPlugin.mSdkApiVersion;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static synchronized int getBuildVerbosity() {
|
||||
if (sPlugin != null) {
|
||||
return sPlugin.mBuildVerbosity;
|
||||
@@ -705,23 +689,25 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
final Display display = getDisplay();
|
||||
|
||||
// we need to ask the user what he wants to do.
|
||||
final BooleanWrapper wrapper = new BooleanWrapper();
|
||||
final boolean[] result = new boolean[1];
|
||||
display.syncExec(new Runnable() {
|
||||
public void run() {
|
||||
Shell shell = display.getActiveShell();
|
||||
wrapper.b = MessageDialog.openQuestion(shell, title, message);
|
||||
result[0] = MessageDialog.openQuestion(shell, title, message);
|
||||
}
|
||||
});
|
||||
return wrapper.b;
|
||||
return result[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a message to the default Eclipse log.
|
||||
*
|
||||
* @param severity The severity code. Valid values are: {@link IStatus#OK}, {@link IStatus#ERROR},
|
||||
* {@link IStatus#INFO}, {@link IStatus#WARNING} or {@link IStatus#CANCEL}.
|
||||
* @param severity The severity code. Valid values are: {@link IStatus#OK},
|
||||
* {@link IStatus#ERROR}, {@link IStatus#INFO}, {@link IStatus#WARNING} or
|
||||
* {@link IStatus#CANCEL}.
|
||||
* @param format The format string, like for {@link String#format(String, Object...)}.
|
||||
* @param args The arguments for the format string, like for {@link String#format(String, Object...)}.
|
||||
* @param args The arguments for the format string, like for
|
||||
* {@link String#format(String, Object...)}.
|
||||
*/
|
||||
public static void log(int severity, String format, Object ... args) {
|
||||
String message = String.format(format, args);
|
||||
@@ -845,7 +831,7 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
|
||||
// now make sure it's not docked.
|
||||
ConsolePlugin.getDefault().getConsoleManager().showConsoleView(
|
||||
CommonPlugin.getDefault().getAndroidConsole());
|
||||
AdtPlugin.getDefault().getAndroidConsole());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -883,21 +869,17 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link IJavaProject} to a list of projects to be recompiled once dx.jar is loaded.
|
||||
* @param javaProject
|
||||
* Returns whether the Sdk has been loaded. If the SDK has not been loaded, the given
|
||||
* <var>project</var> is added to a list of projects to recompile after the SDK is loaded.
|
||||
*/
|
||||
public void addPostDexProject(IJavaProject javaProject) {
|
||||
synchronized (mPostDexProjects) {
|
||||
if (DexWrapper.getStatus() == DexWrapper.LoadStatus.LOADED) {
|
||||
// Setup the new container for each project. By providing new instances of
|
||||
// AndroidClasspathContainer, this will force JDT to call
|
||||
// IClasspathContainer#getClasspathEntries() again and receive the new
|
||||
// path to the framework jar, and the project will be recompiled.
|
||||
AndroidClasspathContainerInitializer.updateProjects(new IJavaProject [] {
|
||||
javaProject });
|
||||
} else {
|
||||
mPostDexProjects.add(javaProject);
|
||||
public LoadStatus getSdkLoadStatus(IJavaProject project) {
|
||||
synchronized (mPostLoadProjects) {
|
||||
// only add the project to the list, if we are still loading.
|
||||
if (mSdkIsLoaded == LoadStatus.LOADING && project != null) {
|
||||
mPostLoadProjects.add(project);
|
||||
}
|
||||
|
||||
return mSdkIsLoaded;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -907,9 +889,6 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
* @return false if the location is not correct.
|
||||
*/
|
||||
private boolean checkSdkLocationAndId() {
|
||||
// Reset the sdk build first in case the SDK is invalid and we abort.
|
||||
mSdkApiVersion = null;
|
||||
|
||||
if (mOsSdkLocation == null || mOsSdkLocation.length() == 0) {
|
||||
displayError(Messages.Dialog_Title_SDK_Location, Messages.SDK_Not_Setup);
|
||||
return false;
|
||||
@@ -949,17 +928,16 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
String.format(Messages.Could_Not_Find_Folder, osSdkLocation));
|
||||
}
|
||||
|
||||
String osTools = osSdkLocation + AndroidConstants.OS_SDK_TOOLS_FOLDER;
|
||||
String osTools = osSdkLocation + SdkConstants.OS_SDK_TOOLS_FOLDER;
|
||||
File toolsFolder = new File(osTools);
|
||||
if (toolsFolder.isDirectory() == false) {
|
||||
return errorHandler.handleError(
|
||||
String.format(Messages.Could_Not_Find_Folder_In_SDK,
|
||||
AndroidConstants.FD_TOOLS, osSdkLocation));
|
||||
SdkConstants.FD_TOOLS, osSdkLocation));
|
||||
}
|
||||
|
||||
// check the path to various tools we use
|
||||
String[] filesToCheck = new String[] {
|
||||
osSdkLocation + getOsRelativeFramework(),
|
||||
osSdkLocation + getOsRelativeAdb(),
|
||||
osSdkLocation + getOsRelativeAapt(),
|
||||
osSdkLocation + getOsRelativeAidl(),
|
||||
@@ -990,118 +968,106 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
}
|
||||
|
||||
/**
|
||||
* Pings the usage start server.
|
||||
* Creates a job than can ping the usage server.
|
||||
*/
|
||||
private void pingUsageServer() {
|
||||
private Job createPingUsageServerJob() {
|
||||
// In order to not block the plugin loading, so we spawn another thread.
|
||||
new Thread("Ping!") { //$NON-NLS-1$
|
||||
Job job = new Job("Android SDK Ping") { // Job name, visible in progress view
|
||||
@Override
|
||||
public void run() {
|
||||
// get the version of the plugin
|
||||
String versionString = (String) getBundle().getHeaders().get(
|
||||
Constants.BUNDLE_VERSION);
|
||||
Version version = new Version(versionString);
|
||||
|
||||
SdkStatsHelper.pingUsageServer("adt", version); //$NON-NLS-1$
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the Editors plugin.
|
||||
* <p/>
|
||||
* Since we do not want any dependencies between the plugins (Editors is an optional
|
||||
* plugin not needed for Android development), we attempt to start the plugin through
|
||||
* OSGi directly.
|
||||
* <p/>
|
||||
* This is done in another thread to not delay the start of this plugin.
|
||||
*/
|
||||
private void startEditorsPlugin() {
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
protected IStatus run(IProgressMonitor monitor) {
|
||||
try {
|
||||
// look for the bundle of the Editors plugin
|
||||
Bundle editorsBundle = Platform.getBundle(AndroidConstants.EDITORS_PLUGIN_ID);
|
||||
if (editorsBundle != null) {
|
||||
// we only start if the bundle is installed and not started.
|
||||
// STARTING means that its start is pending a triggering.
|
||||
int bundleState = editorsBundle.getState();
|
||||
if ((bundleState & (Bundle.RESOLVED | Bundle.INSTALLED
|
||||
| Bundle.STARTING)) != 0) {
|
||||
// Attempt to start it.
|
||||
// START_TRANSIENT is used because we don't want
|
||||
// to change the auto start value.
|
||||
editorsBundle.start(Bundle.START_TRANSIENT);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log(e, Messages.AdtPlugin_Failed_To_Start_s, AndroidConstants.EDITORS_PLUGIN_ID);
|
||||
|
||||
// get the version of the plugin
|
||||
String versionString = (String) getBundle().getHeaders().get(
|
||||
Constants.BUNDLE_VERSION);
|
||||
Version version = new Version(versionString);
|
||||
|
||||
SdkStatsHelper.pingUsageServer("editors", version); //$NON-NLS-1$
|
||||
|
||||
return Status.OK_STATUS;
|
||||
} catch (Throwable t) {
|
||||
log(t, "pingUsageServer failed"); //$NON-NLS-1$
|
||||
return new Status(IStatus.ERROR, PLUGIN_ID,
|
||||
"pingUsageServer failed", t);
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
};
|
||||
return job;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the SDK resources and set them in the {@link FrameworkResourceManager}.
|
||||
* Parses the SDK resources.
|
||||
*/
|
||||
private void parseSdkContent() {
|
||||
// Perform the update in a thread (here an Eclipse runtime job)
|
||||
// since this should never block the caller (especially the start method)
|
||||
new Job(Messages.AdtPlugin_Android_SDK_Content_Loader) {
|
||||
Job job = new Job(Messages.AdtPlugin_Android_SDK_Content_Loader) {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected IStatus run(IProgressMonitor monitor) {
|
||||
try {
|
||||
SubMonitor progress = null;
|
||||
try {
|
||||
progress = SubMonitor.convert(monitor, Messages.AdtPlugin_Parsing_Resources, 100);
|
||||
|
||||
// load the values.
|
||||
FrameworkResourceParser parser = new FrameworkResourceParser();
|
||||
parser.parse(mOsSdkLocation, FrameworkResourceManager.getInstance(),
|
||||
progress);
|
||||
|
||||
// set the location of the layout lib jar file.
|
||||
FrameworkResourceManager.getInstance().setLayoutLibLocation(
|
||||
mOsSdkLocation + AndroidConstants.OS_SDK_LIBS_LAYOUTLIB_JAR);
|
||||
FrameworkResourceManager.getInstance().setFrameworkResourcesLocation(
|
||||
mOsSdkLocation + AndroidConstants.OS_SDK_RESOURCES_FOLDER);
|
||||
FrameworkResourceManager.getInstance().setFrameworkFontLocation(
|
||||
mOsSdkLocation + AndroidConstants.OS_SDK_FONTS_FOLDER);
|
||||
} catch (Throwable e) {
|
||||
AdtPlugin.log(e, "Android SDK Resource Parser failed"); //$NON-NLS-1$
|
||||
AdtPlugin.printErrorToConsole(Messages.AdtPlugin_Android_SDK_Resource_Parser,
|
||||
Messages.AdtPlugin_Failed_To_Parse_s + e.getMessage());
|
||||
|
||||
return new Status(IStatus.ERROR, PLUGIN_ID, e.getMessage(), e);
|
||||
} finally {
|
||||
if (progress != null) {
|
||||
progress.worked(100);
|
||||
}
|
||||
}
|
||||
try {
|
||||
SubMonitor progress = SubMonitor.convert(monitor,
|
||||
"Initialize SDK Manager", 100);
|
||||
|
||||
try {
|
||||
progress = SubMonitor.convert(monitor, Messages.AdtPlugin_Parsing_Resources, 20);
|
||||
Sdk sdk = Sdk.loadSdk(mOsSdkLocation);
|
||||
|
||||
if (sdk != null) {
|
||||
|
||||
progress.setTaskName(Messages.AdtPlugin_Parsing_Resources);
|
||||
|
||||
for (IAndroidTarget target : sdk.getTargets()) {
|
||||
IStatus status = new AndroidTargetParser(target).run(progress);
|
||||
if (status.getCode() != IStatus.OK) {
|
||||
synchronized (mPostLoadProjects) {
|
||||
mSdkIsLoaded = LoadStatus.FAILED;
|
||||
mPostLoadProjects.clear();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: move this per platform, or somewhere else.
|
||||
progress = SubMonitor.convert(monitor,
|
||||
Messages.AdtPlugin_Parsing_Resources, 20);
|
||||
DexWrapper.unloadDex();
|
||||
|
||||
IStatus res = DexWrapper.loadDex(
|
||||
mOsSdkLocation + AndroidConstants.OS_SDK_LIBS_DX_JAR);
|
||||
if (res != Status.OK_STATUS) {
|
||||
synchronized (mPostLoadProjects) {
|
||||
mSdkIsLoaded = LoadStatus.FAILED;
|
||||
mPostLoadProjects.clear();
|
||||
}
|
||||
return res;
|
||||
} else {
|
||||
}
|
||||
|
||||
synchronized (mPostLoadProjects) {
|
||||
mSdkIsLoaded = LoadStatus.LOADED;
|
||||
|
||||
// update the project that needs recompiling.
|
||||
synchronized (mPostDexProjects) {
|
||||
if (mPostDexProjects.size() > 0) {
|
||||
IJavaProject[] array = mPostDexProjects.toArray(
|
||||
new IJavaProject[mPostDexProjects.size()]);
|
||||
AndroidClasspathContainerInitializer.updateProjects(array);
|
||||
mPostDexProjects.clear();
|
||||
}
|
||||
if (mPostLoadProjects.size() > 0) {
|
||||
IJavaProject[] array = mPostLoadProjects.toArray(
|
||||
new IJavaProject[mPostLoadProjects.size()]);
|
||||
AndroidClasspathContainerInitializer.updateProjects(array);
|
||||
mPostLoadProjects.clear();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (progress != null) {
|
||||
progress.worked(20);
|
||||
}
|
||||
|
||||
// Notify resource changed listeners
|
||||
progress.subTask("Refresh UI");
|
||||
progress.setWorkRemaining(mResourceRefreshListener.size());
|
||||
|
||||
// Clone the list before iterating, to avoid Concurrent Modification
|
||||
// exceptions
|
||||
List<Runnable> listeners = (List<Runnable>)mResourceRefreshListener.clone();
|
||||
for (Runnable listener : listeners) {
|
||||
try {
|
||||
AdtPlugin.getDisplay().syncExec(listener);
|
||||
} catch (Exception e) {
|
||||
AdtPlugin.log(e, "ResourceRefreshListener Failed"); //$NON-NLS-1$
|
||||
} finally {
|
||||
progress.worked(1);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@@ -1112,6 +1078,252 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
|
||||
return Status.OK_STATUS;
|
||||
}
|
||||
}.schedule();
|
||||
};
|
||||
job.setPriority(Job.BUILD); // build jobs are run after other interactive jobs
|
||||
job.schedule();
|
||||
}
|
||||
|
||||
/** Returns the global android console */
|
||||
public MessageConsole getAndroidConsole() {
|
||||
return mAndroidConsole;
|
||||
}
|
||||
|
||||
// ----- Methods for Editors -------
|
||||
|
||||
public void startEditors() {
|
||||
sAndroidLogoDesc = imageDescriptorFromPlugin(AdtPlugin.PLUGIN_ID,
|
||||
"/icons/android.png"); //$NON-NLS-1$
|
||||
sAndroidLogo = sAndroidLogoDesc.createImage();
|
||||
|
||||
// get the stream to write in the android console.
|
||||
MessageConsole androidConsole = AdtPlugin.getDefault().getAndroidConsole();
|
||||
mAndroidConsoleStream = androidConsole.newMessageStream();
|
||||
|
||||
mAndroidConsoleErrorStream = androidConsole.newMessageStream();
|
||||
mRed = new Color(getDisplay(), 0xFF, 0x00, 0x00);
|
||||
|
||||
// because this can be run, in some cases, by a non ui thread, and beccause
|
||||
// changing the console properties update the ui, we need to make this change
|
||||
// in the ui thread.
|
||||
getDisplay().asyncExec(new Runnable() {
|
||||
public void run() {
|
||||
mAndroidConsoleErrorStream.setColor(mRed);
|
||||
}
|
||||
});
|
||||
|
||||
// Add a resource listener to handle compiled resources.
|
||||
IWorkspace ws = ResourcesPlugin.getWorkspace();
|
||||
mResourceMonitor = ResourceMonitor.startMonitoring(ws);
|
||||
|
||||
if (mResourceMonitor != null) {
|
||||
try {
|
||||
setupDefaultEditor(mResourceMonitor);
|
||||
ResourceManager.setup(mResourceMonitor);
|
||||
} catch (Throwable t) {
|
||||
log(t, "ResourceManager.setup failed"); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The <code>AbstractUIPlugin</code> implementation of this <code>Plugin</code>
|
||||
* method saves this plug-in's preference and dialog stores and shuts down
|
||||
* its image registry (if they are in use). Subclasses may extend this
|
||||
* method, but must send super <b>last</b>. A try-finally statement should
|
||||
* be used where necessary to ensure that <code>super.shutdown()</code> is
|
||||
* always done.
|
||||
*
|
||||
* @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
|
||||
*/
|
||||
public void stopEditors() {
|
||||
sAndroidLogo.dispose();
|
||||
|
||||
IconFactory.getInstance().Dispose();
|
||||
|
||||
// Remove the resource listener that handles compiled resources.
|
||||
IWorkspace ws = ResourcesPlugin.getWorkspace();
|
||||
ResourceMonitor.stopMonitoring(ws);
|
||||
|
||||
mRed.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Image for the small Android logo.
|
||||
*
|
||||
* Callers should not dispose it.
|
||||
*/
|
||||
public static Image getAndroidLogo() {
|
||||
return sAndroidLogo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link ImageDescriptor} for the small Android logo.
|
||||
*
|
||||
* Callers should not dispose it.
|
||||
*/
|
||||
public static ImageDescriptor getAndroidLogoDesc() {
|
||||
return sAndroidLogoDesc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ResourceMonitor object.
|
||||
*/
|
||||
public ResourceMonitor getResourceMonitor() {
|
||||
return mResourceMonitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the editor to register default editors for resource files when needed.
|
||||
*
|
||||
* This is called by the {@link AdtPlugin} during initialization.
|
||||
*
|
||||
* @param monitor The main Resource Monitor object.
|
||||
*/
|
||||
public void setupDefaultEditor(ResourceMonitor monitor) {
|
||||
monitor.addFileListener(new IFileListener() {
|
||||
|
||||
private static final String UNKNOWN_EDITOR = "unknown-editor"; //$NON-NLS-1$
|
||||
|
||||
/* (non-Javadoc)
|
||||
* Sent when a file changed.
|
||||
* @param file The file that changed.
|
||||
* @param markerDeltas The marker deltas for the file.
|
||||
* @param kind The change kind. This is equivalent to
|
||||
* {@link IResourceDelta#accept(IResourceDeltaVisitor)}
|
||||
*
|
||||
* @see IFileListener#fileChanged
|
||||
*/
|
||||
public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) {
|
||||
if (AndroidConstants.EXT_XML.equals(file.getFileExtension())) {
|
||||
// The resources files must have a file path similar to
|
||||
// project/res/.../*.xml
|
||||
// There is no support for sub folders, so the segment count must be 4
|
||||
if (file.getFullPath().segmentCount() == 4) {
|
||||
// check if we are inside the res folder.
|
||||
String segment = file.getFullPath().segment(1);
|
||||
if (segment.equalsIgnoreCase(AndroidConstants.FD_RESOURCES)) {
|
||||
// we are inside a res/ folder, get the actual ResourceFolder
|
||||
ProjectResources resources = ResourceManager.getInstance().
|
||||
getProjectResources(file.getProject());
|
||||
|
||||
// This happens when importing old Android projects in Eclipse
|
||||
// that lack the container (probably because resources fail to build
|
||||
// properly.)
|
||||
if (resources == null) {
|
||||
log(IStatus.INFO,
|
||||
"getProjectResources failed for path %1$s in project %2$s", //$NON-NLS-1$
|
||||
file.getFullPath().toOSString(),
|
||||
file.getProject().getName());
|
||||
return;
|
||||
}
|
||||
|
||||
ResourceFolder resFolder = resources.getResourceFolder(
|
||||
(IFolder)file.getParent());
|
||||
|
||||
if (resFolder != null) {
|
||||
if (kind == IResourceDelta.ADDED) {
|
||||
resourceAdded(file, resFolder.getType());
|
||||
} else if (kind == IResourceDelta.CHANGED) {
|
||||
resourceChanged(file, resFolder.getType());
|
||||
}
|
||||
} else {
|
||||
// if the res folder is null, this means the name is invalid,
|
||||
// in this case we remove whatever android editors that was set
|
||||
// as the default editor.
|
||||
IEditorDescriptor desc = IDE.getDefaultEditor(file);
|
||||
String editorId = desc.getId();
|
||||
if (editorId.startsWith(AndroidConstants.EDITORS_NAMESPACE)) {
|
||||
// reset the default editor.
|
||||
IDE.setDefaultEditor(file, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resourceAdded(IFile file, ResourceFolderType type) {
|
||||
// set the default editor based on the type.
|
||||
if (type == ResourceFolderType.LAYOUT) {
|
||||
IDE.setDefaultEditor(file, LayoutEditor.ID);
|
||||
} else if (type == ResourceFolderType.DRAWABLE
|
||||
|| type == ResourceFolderType.VALUES) {
|
||||
IDE.setDefaultEditor(file, ResourcesEditor.ID);
|
||||
} else if (type == ResourceFolderType.MENU) {
|
||||
IDE.setDefaultEditor(file, MenuEditor.ID);
|
||||
} else if (type == ResourceFolderType.XML) {
|
||||
if (XmlEditor.canHandleFile(file)) {
|
||||
IDE.setDefaultEditor(file, XmlEditor.ID);
|
||||
} else {
|
||||
// set a property to determine later if the XML can be handled
|
||||
QualifiedName qname = new QualifiedName(
|
||||
AdtPlugin.PLUGIN_ID,
|
||||
UNKNOWN_EDITOR);
|
||||
try {
|
||||
file.setPersistentProperty(qname, "1");
|
||||
} catch (CoreException e) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resourceChanged(IFile file, ResourceFolderType type) {
|
||||
if (type == ResourceFolderType.XML) {
|
||||
IEditorDescriptor ed = IDE.getDefaultEditor(file);
|
||||
if (ed == null || ed.getId() != XmlEditor.ID) {
|
||||
QualifiedName qname = new QualifiedName(
|
||||
AdtPlugin.PLUGIN_ID,
|
||||
UNKNOWN_EDITOR);
|
||||
String prop = null;
|
||||
try {
|
||||
prop = file.getPersistentProperty(qname);
|
||||
} catch (CoreException e) {
|
||||
// pass
|
||||
}
|
||||
if (prop != null && XmlEditor.canHandleFile(file)) {
|
||||
try {
|
||||
// remove the property & set editor
|
||||
file.setPersistentProperty(qname, null);
|
||||
IWorkbenchPage page = PlatformUI.getWorkbench().
|
||||
getActiveWorkbenchWindow().getActivePage();
|
||||
|
||||
IEditorPart oldEditor = page.findEditor(new FileEditorInput(file));
|
||||
if (oldEditor != null &&
|
||||
AdtPlugin.displayPrompt("Android XML Editor",
|
||||
String.format("The file you just saved as been recognized as a file that could be better handled using the Android XML Editor. Do you want to edit '%1$s' using the Android XML editor instead?",
|
||||
file.getFullPath()))) {
|
||||
IDE.setDefaultEditor(file, XmlEditor.ID);
|
||||
IEditorPart newEditor = page.openEditor(
|
||||
new FileEditorInput(file),
|
||||
XmlEditor.ID,
|
||||
true, /* activate */
|
||||
IWorkbenchPage.MATCH_NONE);
|
||||
|
||||
if (newEditor != null) {
|
||||
page.closeEditor(oldEditor, true /* save */);
|
||||
}
|
||||
}
|
||||
} catch (CoreException e) {
|
||||
// setPersistentProperty or page.openEditor may have failed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}, IResourceDelta.ADDED | IResourceDelta.CHANGED);
|
||||
}
|
||||
|
||||
public void addResourceChangedListener(Runnable resourceRefreshListener) {
|
||||
mResourceRefreshListener.add(resourceRefreshListener);
|
||||
}
|
||||
|
||||
public void removeResourceChangedListener(Runnable resourceRefreshListener) {
|
||||
mResourceRefreshListener.remove(resourceRefreshListener);
|
||||
}
|
||||
|
||||
public static synchronized OutputStream getErrorStream() {
|
||||
return sPlugin.mAndroidConsoleErrorStream;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,8 @@
|
||||
|
||||
package com.android.ide.eclipse.adt;
|
||||
|
||||
import com.android.ddmlib.Device;
|
||||
import com.android.ide.eclipse.adt.AdtPlugin.CheckSdkErrorHandler;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.sdklib.SdkConstants;
|
||||
|
||||
import org.osgi.framework.Constants;
|
||||
import org.osgi.framework.Version;
|
||||
@@ -42,19 +41,6 @@ import java.util.regex.Pattern;
|
||||
*
|
||||
*/
|
||||
final class VersionCheck {
|
||||
|
||||
/** Pattern to get the SDK build incremental version from the
|
||||
* <code>$SDK/tools/lib/build.prop file</code>. */
|
||||
private final static Pattern sBuildVersionPattern = Pattern.compile(
|
||||
"^" + Device.PROP_BUILD_VERSION + "=(.+)$"); //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Pattern to parse release type SDK version number. This parses the content read with
|
||||
* <code>sBuildIdPattern</code>.
|
||||
*/
|
||||
private final static Pattern sSdkVersionPattern = Pattern.compile(
|
||||
"^(\\d+)\\.(\\d+)_r(\\d+)$", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Pattern to get the minimum plugin version supported by the SDK. This is read from
|
||||
* the file <code>$SDK/tools/lib/plugin.prop</code>.
|
||||
@@ -69,58 +55,7 @@ final class VersionCheck {
|
||||
*/
|
||||
public static boolean checkVersion(String osSdkPath, CheckSdkErrorHandler errorHandler) {
|
||||
AdtPlugin plugin = AdtPlugin.getDefault();
|
||||
String osLibs = osSdkPath + AndroidConstants.OS_SDK_LIBS_FOLDER;
|
||||
|
||||
/*
|
||||
* All plugins should work with all SDKs. Newer SDKs may require a newer plugin
|
||||
* but this is handled below.
|
||||
* Still, we need to grab the SDK version from this file. This is used
|
||||
* to compare to running emulator/device when launching run/debug sessions.
|
||||
*/
|
||||
try {
|
||||
FileReader reader = new FileReader(osLibs + AndroidConstants.FN_BUILD_PROP);
|
||||
BufferedReader bReader = new BufferedReader(reader);
|
||||
String line;
|
||||
while ((line = bReader.readLine()) != null) {
|
||||
Matcher m = sBuildVersionPattern.matcher(line);
|
||||
if (m.matches()) {
|
||||
plugin.mSdkApiVersion = m.group(1).trim();
|
||||
|
||||
/*
|
||||
* No checks on the version at the moment.
|
||||
*/
|
||||
/*
|
||||
if (plugin.mSdkBuildVersion != null) {
|
||||
// attempt to get version number from the build id
|
||||
m = sSdkVersionPattern.matcher(plugin.mSdkBuildVersion);
|
||||
if (m.matches()) {
|
||||
// get the platform version number
|
||||
int platformMajor = Integer.parseInt(m.group(1));
|
||||
int platformMinor = Integer.parseInt(m.group(2));
|
||||
@SuppressWarnings("unused") //$NON-NLS-1$
|
||||
int sdkRelease = Integer.parseInt(m.group(3));
|
||||
|
||||
if (platformMajor != 0 || platformMinor != 9) {
|
||||
return errorHandler.handleError(String.format(
|
||||
"This version of ADT requires the Android SDK version 0.9\n\nCurrent version is %1$s.\n\nPlease update your SDK to the latest version.",
|
||||
plugin.mSdkBuildVersion));
|
||||
}
|
||||
} else {
|
||||
// unknown version format.
|
||||
AdtPlugin.printErrorToConsole(
|
||||
(Object)String.format(Messages.VersionCheck_Unable_To_Parse_Version_s,
|
||||
plugin.mSdkBuildVersion));
|
||||
}
|
||||
}
|
||||
*/
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// the build id will be null, and this is handled by the builders.
|
||||
} catch (IOException e) {
|
||||
// the build id will be null, and this is handled by the builders.
|
||||
}
|
||||
String osLibs = osSdkPath + SdkConstants.OS_SDK_TOOLS_LIB_FOLDER;
|
||||
|
||||
// get the plugin property file, and grab the minimum plugin version required
|
||||
// to work with the sdk
|
||||
@@ -128,7 +63,7 @@ final class VersionCheck {
|
||||
int minMinorVersion = -1;
|
||||
int minMicroVersion = -1;
|
||||
try {
|
||||
FileReader reader = new FileReader(osLibs + AndroidConstants.FN_PLUGIN_PROP);
|
||||
FileReader reader = new FileReader(osLibs + SdkConstants.FN_PLUGIN_PROP);
|
||||
BufferedReader bReader = new BufferedReader(reader);
|
||||
String line;
|
||||
while ((line = bReader.readLine()) != null) {
|
||||
|
||||
@@ -19,6 +19,8 @@ package com.android.ide.eclipse.adt.build;
|
||||
import com.android.ide.eclipse.adt.AdtConstants;
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.project.ProjectHelper;
|
||||
import com.android.ide.eclipse.adt.sdk.LoadStatus;
|
||||
import com.android.ide.eclipse.adt.sdk.Sdk;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.common.project.BaseProjectHelper;
|
||||
import com.android.jarutils.DebugKeyProvider;
|
||||
@@ -28,6 +30,7 @@ import com.android.jarutils.DebugKeyProvider.IKeyGenOutput;
|
||||
import com.android.jarutils.DebugKeyProvider.KeytoolException;
|
||||
import com.android.jarutils.SignedJarBuilder.IZipEntryFilter;
|
||||
import com.android.prefs.AndroidLocation.AndroidLocationException;
|
||||
import com.android.sdklib.IAndroidTarget;
|
||||
|
||||
import org.eclipse.core.resources.IContainer;
|
||||
import org.eclipse.core.resources.IFile;
|
||||
@@ -44,6 +47,7 @@ import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.Path;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jdt.core.JavaCore;
|
||||
@@ -296,6 +300,15 @@ public class ApkBuilder extends BaseBuilder {
|
||||
saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
|
||||
saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);
|
||||
|
||||
// At this point, we can abort the build if we have to, as we have computed
|
||||
// our resource delta and stored the result.
|
||||
|
||||
// check if we have finished loading the SDK.
|
||||
if (AdtPlugin.getDefault().getSdkLoadStatus(javaProject) != LoadStatus.LOADED) {
|
||||
// we exit silently
|
||||
return referencedProjects;
|
||||
}
|
||||
|
||||
// Now check the compiler compliance level, not displaying the error
|
||||
// message since this is not the first builder.
|
||||
if (ProjectHelper.checkCompilerCompliance(getProject())
|
||||
@@ -502,7 +515,8 @@ public class ApkBuilder extends BaseBuilder {
|
||||
commandArray.add(osAssetsPath);
|
||||
}
|
||||
commandArray.add("-I"); //$NON-NLS-1$
|
||||
commandArray.add(AdtPlugin.getOsAbsoluteFramework());
|
||||
commandArray.add(
|
||||
Sdk.getCurrent().getTarget(project).getPath(IAndroidTarget.ANDROID_JAR));
|
||||
commandArray.add("-F"); //$NON-NLS-1$
|
||||
commandArray.add(osOutFilePath);
|
||||
|
||||
@@ -584,16 +598,9 @@ public class ApkBuilder extends BaseBuilder {
|
||||
DexWrapper wrapper = DexWrapper.getWrapper();
|
||||
|
||||
if (wrapper == null) {
|
||||
if (DexWrapper.getStatus() == DexWrapper.LoadStatus.FAILED) {
|
||||
if (DexWrapper.getStatus() == LoadStatus.FAILED) {
|
||||
throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
|
||||
Messages.ApkBuilder_UnableBuild_Dex_Not_loaded));
|
||||
} else {
|
||||
// means we haven't loaded the dex jar yet.
|
||||
// We set the project to be recompiled after dex is loaded.
|
||||
AdtPlugin.getDefault().addPostDexProject(javaProject);
|
||||
|
||||
// and we exit silently
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -677,7 +684,7 @@ public class ApkBuilder extends BaseBuilder {
|
||||
}
|
||||
|
||||
// TODO: get the store type from somewhere else.
|
||||
DebugKeyProvider provider = new DebugKeyProvider(null /* storeType */,
|
||||
DebugKeyProvider provider = new DebugKeyProvider(osKeyPath, null /* storeType */,
|
||||
new IKeyGenOutput() {
|
||||
public void err(String message) {
|
||||
AdtPlugin.printErrorToConsole(javaProject.getProject(),
|
||||
@@ -747,9 +754,18 @@ public class ApkBuilder extends BaseBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// now write the native libraries.
|
||||
// First look if the lib folder is there.
|
||||
IResource libFolder = javaProject.getProject().findMember(
|
||||
AndroidConstants.FD_NATIVE_LIBS);
|
||||
if (libFolder != null && libFolder.exists() &&
|
||||
libFolder.getType() == IResource.FOLDER) {
|
||||
// look inside and put .so in lib/* by keeping the relative folder path.
|
||||
writeNativeLibraries(libFolder.getFullPath().segmentCount(), builder, libFolder);
|
||||
}
|
||||
|
||||
// close the jar file and write the manifest and sign it.
|
||||
builder.close();
|
||||
|
||||
} catch (GeneralSecurityException e1) {
|
||||
// mark project and return
|
||||
String msg = String.format(Messages.Final_Archive_Error_s, e1.getMessage());
|
||||
@@ -784,6 +800,12 @@ public class ApkBuilder extends BaseBuilder {
|
||||
|
||||
// and also output it in the console
|
||||
AdtPlugin.printErrorToConsole(javaProject.getProject(), msg);
|
||||
} catch (CoreException e) {
|
||||
// mark project and return
|
||||
String msg = String.format(Messages.Final_Archive_Error_s, e.getMessage());
|
||||
AdtPlugin.printErrorToConsole(javaProject.getProject(), msg);
|
||||
markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
|
||||
return false;
|
||||
} finally {
|
||||
if (fos != null) {
|
||||
try {
|
||||
@@ -797,6 +819,48 @@ public class ApkBuilder extends BaseBuilder {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes native libraries into a {@link SignedJarBuilder}.
|
||||
* <p/>This recursively go through folder and writes .so files.
|
||||
* The path in the archive is based on the root folder containing the libraries in the project.
|
||||
* Its segment count is passed to the method to compute the resources path relative to the root
|
||||
* folder.
|
||||
* Native libraries in the archive must be in a "lib" folder. Everything in the project native
|
||||
* lib folder directly goes in this "lib" folder in the archive.
|
||||
*
|
||||
*
|
||||
* @param rooSegmentCount The number of segment of the path of the folder containing the
|
||||
* libraries. This is used to compute the path in the archive.
|
||||
* @param jarBuilder the {@link SignedJarBuilder} used to create the archive.
|
||||
* @param resource the IResource to write.
|
||||
* @throws CoreException
|
||||
* @throws IOException
|
||||
*/
|
||||
private void writeNativeLibraries(int rootSegmentCount, SignedJarBuilder jarBuilder,
|
||||
IResource resource) throws CoreException, IOException {
|
||||
if (resource.getType() == IResource.FILE) {
|
||||
IPath path = resource.getFullPath();
|
||||
|
||||
// check the extension.
|
||||
if (path.getFileExtension().equalsIgnoreCase(AndroidConstants.EXT_NATIVE_LIB)) {
|
||||
// remove the first segment to build the path inside the archive.
|
||||
path = path.removeFirstSegments(rootSegmentCount);
|
||||
|
||||
// add it to the archive.
|
||||
IPath apkPath = new Path(AndroidConstants.FD_APK_NATIVE_LIBS);
|
||||
apkPath = apkPath.append(path);
|
||||
|
||||
// writes the file in the apk.
|
||||
jarBuilder.writeFile(resource.getLocation().toFile(), apkPath.toString());
|
||||
}
|
||||
} else if (resource.getType() == IResource.FOLDER) {
|
||||
IResource[] members = ((IFolder)resource).members();
|
||||
for (IResource member : members) {
|
||||
writeNativeLibraries(rootSegmentCount, jarBuilder, member);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the standard resources of a project and its referenced projects
|
||||
* into a {@link SignedJarBuilder}.
|
||||
|
||||
@@ -42,6 +42,7 @@ import java.util.ArrayList;
|
||||
* <li>Any change to the classes.dex inside the output folder</li>
|
||||
* <li>Any change to the packaged resources file inside the output folder</li>
|
||||
* <li>Any change to a non java/aidl file inside the source folders</li>
|
||||
* <li>Any change to .so file inside the lib (native library) folder</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class ApkDeltaVisitor extends BaseDeltaVisitor
|
||||
@@ -79,6 +80,8 @@ public class ApkDeltaVisitor extends BaseDeltaVisitor
|
||||
|
||||
private IPath mResPath;
|
||||
|
||||
private IPath mLibFolder;
|
||||
|
||||
/**
|
||||
* Builds the object with a specified output folder.
|
||||
* @param builder the xml builder using this object to visit the
|
||||
@@ -104,6 +107,11 @@ public class ApkDeltaVisitor extends BaseDeltaVisitor
|
||||
if (resFolder != null) {
|
||||
mResPath = resFolder.getFullPath();
|
||||
}
|
||||
|
||||
IResource libFolder = builder.getProject().findMember(AndroidConstants.FD_NATIVE_LIBS);
|
||||
if (libFolder != null) {
|
||||
mLibFolder = libFolder.getFullPath();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getConvertToDex() {
|
||||
@@ -161,6 +169,7 @@ public class ApkDeltaVisitor extends BaseDeltaVisitor
|
||||
|
||||
// check the other folders.
|
||||
if (mOutputPath != null && mOutputPath.isPrefixOf(path)) {
|
||||
// a resource changed inside the output folder.
|
||||
if (type == IResource.FILE) {
|
||||
// just check this is a .class file. Any modification will
|
||||
// trigger a change in the classes.dex file
|
||||
@@ -217,6 +226,17 @@ public class ApkDeltaVisitor extends BaseDeltaVisitor
|
||||
mPackageResources = true;
|
||||
mMakeFinalPackage = true;
|
||||
return false;
|
||||
} else if (mLibFolder != null && mLibFolder.isPrefixOf(path)) {
|
||||
// inside the native library folder. Test if the changed resource is a .so file.
|
||||
if (type == IResource.FILE &&
|
||||
path.getFileExtension().equalsIgnoreCase(AndroidConstants.EXT_NATIVE_LIB)) {
|
||||
mMakeFinalPackage = true;
|
||||
return false; // return false for file.
|
||||
}
|
||||
|
||||
// for folders, return true only if we don't already know we have to make the
|
||||
// final package.
|
||||
return mMakeFinalPackage == false;
|
||||
} else {
|
||||
// we are in a folder that is neither the resource folders, nor the output.
|
||||
// check against all the source folders, unless we already know we need to do
|
||||
|
||||
@@ -806,12 +806,6 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
|
||||
// get the IPath
|
||||
IPath path = e.getPath();
|
||||
|
||||
// get the file name. if it's the framework jar, we ignore that file.
|
||||
// since we now use classpath container, this is here for legacy purpose only.
|
||||
if (AndroidConstants.FN_FRAMEWORK_LIBRARY.equals(path.lastSegment())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check the name ends with .jar
|
||||
if (AndroidConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) {
|
||||
boolean local = false;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.ide.eclipse.adt.build;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.sdk.LoadStatus;
|
||||
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
@@ -47,9 +48,6 @@ public final class DexWrapper {
|
||||
|
||||
private static DexWrapper sWrapper;
|
||||
|
||||
/** Status for the Loading of the dex jar file */
|
||||
public enum LoadStatus { LOADING, LOADED, FAILED }
|
||||
|
||||
private static LoadStatus sLoadStatus = LoadStatus.LOADING;
|
||||
|
||||
private Method mRunMethod;
|
||||
|
||||
@@ -18,14 +18,16 @@ package com.android.ide.eclipse.adt.build;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtConstants;
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.editors.java.ReadOnlyJavaEditor;
|
||||
import com.android.ide.eclipse.adt.project.FixLaunchConfig;
|
||||
import com.android.ide.eclipse.adt.project.ProjectHelper;
|
||||
import com.android.ide.eclipse.adt.sdk.LoadStatus;
|
||||
import com.android.ide.eclipse.adt.sdk.Sdk;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.common.project.AndroidManifestHelper;
|
||||
import com.android.ide.eclipse.common.project.AndroidManifestParser;
|
||||
import com.android.ide.eclipse.common.project.BaseProjectHelper;
|
||||
import com.android.ide.eclipse.common.project.XmlErrorHandler.BasicXmlErrorListener;
|
||||
import com.android.sdklib.IAndroidTarget;
|
||||
|
||||
import org.eclipse.core.resources.IContainer;
|
||||
import org.eclipse.core.resources.IFile;
|
||||
@@ -44,7 +46,6 @@ import org.eclipse.core.runtime.NullProgressMonitor;
|
||||
import org.eclipse.core.runtime.Path;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jdt.core.JavaCore;
|
||||
import org.eclipse.ui.ide.IDE;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
@@ -130,7 +131,6 @@ public class PreCompilerBuilder extends BaseBuilder {
|
||||
mSource);
|
||||
try {
|
||||
mNewFile.setDerived(true);
|
||||
IDE.setDefaultEditor(mNewFile, ReadOnlyJavaEditor.ID);
|
||||
} catch (CoreException e) {
|
||||
// This really shouldn't happen since we check that the resource exist.
|
||||
// Worst case scenario, the resource isn't marked as derived.
|
||||
@@ -266,8 +266,14 @@ public class PreCompilerBuilder extends BaseBuilder {
|
||||
|
||||
// record the state
|
||||
mCompileResources |= dv.getCompileResources();
|
||||
mergeAidlFileModifications(dv.getAidlToCompile(),
|
||||
dv.getAidlToRemove());
|
||||
|
||||
// handle aidl modification
|
||||
if (dv.getFullAidlRecompilation()) {
|
||||
buildAidlCompilationList(project, sourceList);
|
||||
} else {
|
||||
mergeAidlFileModifications(dv.getAidlToCompile(),
|
||||
dv.getAidlToRemove());
|
||||
}
|
||||
|
||||
// if there was some XML errors, we just return w/o doing
|
||||
// anything since we've put some markers in the files anyway.
|
||||
@@ -290,6 +296,12 @@ public class PreCompilerBuilder extends BaseBuilder {
|
||||
// At this point we have stored what needs to be build, so we can
|
||||
// do some high level test and abort if needed.
|
||||
|
||||
// check if we have finished loading the SDK.
|
||||
if (AdtPlugin.getDefault().getSdkLoadStatus(javaProject) != LoadStatus.LOADED) {
|
||||
// we exit silently
|
||||
return null;
|
||||
}
|
||||
|
||||
// check the compiler compliance level, not displaying the error message
|
||||
// since this is not the first builder.
|
||||
if (ProjectHelper.checkCompilerCompliance(getProject())
|
||||
@@ -302,13 +314,19 @@ public class PreCompilerBuilder extends BaseBuilder {
|
||||
// Check that the SDK directory has been setup.
|
||||
String osSdkFolder = AdtPlugin.getOsSdkFolder();
|
||||
|
||||
if (osSdkFolder.length() == 0) {
|
||||
if (osSdkFolder == null || osSdkFolder.length() == 0) {
|
||||
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
|
||||
Messages.No_SDK_Setup_Error);
|
||||
markProject(AdtConstants.MARKER_ADT, Messages.No_SDK_Setup_Error,
|
||||
IMarker.SEVERITY_ERROR);
|
||||
return null;
|
||||
}
|
||||
|
||||
IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project);
|
||||
if (projectTarget == null) {
|
||||
// no target. error has been output by the container initializer: exit silently.
|
||||
return null;
|
||||
}
|
||||
|
||||
// get the manifest file
|
||||
IFile manifest = AndroidManifestHelper.getManifest(project);
|
||||
@@ -448,7 +466,7 @@ public class PreCompilerBuilder extends BaseBuilder {
|
||||
array.add("-S"); //$NON-NLS-1$
|
||||
array.add(osResPath);
|
||||
array.add("-I"); //$NON-NLS-1$
|
||||
array.add(AdtPlugin.getOsAbsoluteFramework());
|
||||
array.add(projectTarget.getPath(IAndroidTarget.ANDROID_JAR));
|
||||
|
||||
if (AdtPlugin.getBuildVerbosity() == AdtConstants.BUILD_VERBOSE) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
@@ -563,6 +581,8 @@ public class PreCompilerBuilder extends BaseBuilder {
|
||||
deleteObsoleteGeneratedClass(AndroidConstants.FN_MANIFEST_CLASS,
|
||||
mManifestPackageSourceFolder, mManifestPackage);
|
||||
}
|
||||
|
||||
// FIXME: delete all java generated from aidl.
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -736,12 +756,14 @@ public class PreCompilerBuilder extends BaseBuilder {
|
||||
if (mAidlToCompile.size() == 0 && mAidlToRemove.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// create the command line
|
||||
String[] command = new String[4 + sourceFolders.size() + (folderAidlPath != null ? 1 : 0)];
|
||||
int index = 0;
|
||||
int aidlIndex;
|
||||
command[index++] = AdtPlugin.getOsAbsoluteAidl();
|
||||
command[index++] = "-p" + AdtPlugin.getOsAbsoluteFrameworkAidl(); //$NON-NLS-1$
|
||||
command[aidlIndex = index++] = "-p"; //$NON-NLS-1$
|
||||
if (folderAidlPath != null) {
|
||||
command[index++] = "-p" + folderAidlPath; //$NON-NLS-1$
|
||||
}
|
||||
@@ -813,6 +835,8 @@ public class PreCompilerBuilder extends BaseBuilder {
|
||||
prepareFileForExternalModification(javaFile);
|
||||
|
||||
// finish to set the command line.
|
||||
command[aidlIndex] = "-p" + Sdk.getCurrent().getTarget(aidlFile.getProject()).getPath(
|
||||
IAndroidTarget.ANDROID_AIDL); //$NON-NLS-1$
|
||||
command[index] = osPath;
|
||||
command[index + 1] = osJavaPath;
|
||||
|
||||
@@ -922,7 +946,8 @@ public class PreCompilerBuilder extends BaseBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes through the buildpath and fills the list of aidl files to compile.
|
||||
* Goes through the build paths and fills the list of aidl files to compile
|
||||
* ({@link #mAidlToCompile}).
|
||||
* @param project The project.
|
||||
* @param buildPaths The list of build paths.
|
||||
*/
|
||||
@@ -977,7 +1002,7 @@ public class PreCompilerBuilder extends BaseBuilder {
|
||||
scanContainerForAidl((IFolder)r);
|
||||
break;
|
||||
default:
|
||||
// this would mean it's a project or the workspaceroot
|
||||
// this would mean it's a project or the workspace root
|
||||
// which is unlikely to happen. we do nothing
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ import java.util.ArrayList;
|
||||
class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
|
||||
IResourceDeltaVisitor {
|
||||
|
||||
// Result fields.
|
||||
/**
|
||||
* Compile flag. This is set to true if one of the changed/added/removed
|
||||
* file is a resource file. Upon visiting all the delta resources, if
|
||||
@@ -50,6 +51,22 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
|
||||
*/
|
||||
private boolean mCompileResources = false;
|
||||
|
||||
/** List of .aidl files found that are modified or new. */
|
||||
private final ArrayList<IFile> mAidlToCompile = new ArrayList<IFile>();
|
||||
|
||||
/** List of .aidl files that have been removed. */
|
||||
private final ArrayList<IFile> mAidlToRemove = new ArrayList<IFile>();
|
||||
|
||||
/** Aidl forced recompilation flag. This is set to true if project.aidl is modified. */
|
||||
private boolean mFullAidlCompilation = false;
|
||||
|
||||
/** Manifest check/parsing flag. */
|
||||
private boolean mCheckedManifestXml = false;
|
||||
|
||||
/** Application Pacakge, gathered from the parsing of the manifest */
|
||||
private String mJavaPackage = null;
|
||||
|
||||
// Internal usage fields.
|
||||
/**
|
||||
* In Resource folder flag. This allows us to know if we're in the
|
||||
* resource folder.
|
||||
@@ -65,14 +82,6 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
|
||||
/** List of source folders. */
|
||||
private ArrayList<IPath> mSourceFolders;
|
||||
|
||||
/** List of .aidl files found that are modified or new. */
|
||||
private final ArrayList<IFile> mAidlToCompile = new ArrayList<IFile>();
|
||||
|
||||
/** List of .aidl files that have been removed. */
|
||||
private final ArrayList<IFile> mAidlToRemove = new ArrayList<IFile>();
|
||||
|
||||
private boolean mCheckedManifestXml = false;
|
||||
private String mJavaPackage = null;
|
||||
|
||||
public PreCompilerDeltaVisitor(BaseBuilder builder, ArrayList<IPath> sourceFolders) {
|
||||
super(builder);
|
||||
@@ -90,6 +99,10 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
|
||||
public ArrayList<IFile> getAidlToRemove() {
|
||||
return mAidlToRemove;
|
||||
}
|
||||
|
||||
public boolean getFullAidlRecompilation() {
|
||||
return mFullAidlCompilation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the manifest file was parsed/checked for error during the resource delta
|
||||
@@ -100,7 +113,7 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the manifest package if the manifest was checked.
|
||||
* Returns the manifest package if the manifest was checked/parsed.
|
||||
* <p/>
|
||||
* This can return null in two cases:
|
||||
* <ul>
|
||||
@@ -169,11 +182,14 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
|
||||
// we don't want to go to the children, not like they are
|
||||
// any for this resource anyway.
|
||||
return false;
|
||||
} else if (AndroidConstants.FN_PROJECT_AIDL.equalsIgnoreCase(segments[1])) {
|
||||
// need to force recompilation of all the aidl files
|
||||
mFullAidlCompilation = true;
|
||||
}
|
||||
}
|
||||
|
||||
// at this point we can either be in the source folder or in the
|
||||
// resource foler or in a different folder that contains a source
|
||||
// resource folder or in a different folder that contains a source
|
||||
// folder.
|
||||
// This is due to not all source folder being src/. Some could be
|
||||
// something/somethingelse/src/
|
||||
@@ -227,7 +243,8 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
|
||||
PreCompilerBuilder.PROPERTY_ANDROID_CONFLICT, true);
|
||||
}
|
||||
|
||||
// we add it anyway so that we can try to compile it every time.
|
||||
// we add it anyway so that we can try to compile it at every compilation
|
||||
// until the conflict is fixed.
|
||||
mAidlToCompile.add(file);
|
||||
|
||||
} else {
|
||||
@@ -245,6 +262,8 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
|
||||
|
||||
boolean outputMessage = false;
|
||||
|
||||
// Special case of R.java/Manifest.java.
|
||||
// FIXME: This does not check the package. Any modification of R.java/Manifest.java in another project will trigger a new recompilation of the resources.
|
||||
if (AndroidConstants.FN_RESOURCE_CLASS.equals(fileName) ||
|
||||
AndroidConstants.FN_MANIFEST_CLASS.equals(fileName)) {
|
||||
// if it was removed, there's a possibility that it was removed due to a
|
||||
|
||||
@@ -62,13 +62,10 @@ public class ResourceManagerBuilder extends IncrementalProjectBuilder {
|
||||
switch (res) {
|
||||
case ProjectHelper.COMPILER_COMPLIANCE_LEVEL:
|
||||
errorMessage = Messages.Requires_Compiler_Compliance_5;
|
||||
return null;
|
||||
case ProjectHelper.COMPILER_COMPLIANCE_SOURCE:
|
||||
errorMessage = Messages.Requires_Source_Compatibility_5;
|
||||
return null;
|
||||
case ProjectHelper.COMPILER_COMPLIANCE_CODEGEN_TARGET:
|
||||
errorMessage = Messages.Requires_Class_Compatibility_5;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (errorMessage != null) {
|
||||
|
||||
@@ -30,9 +30,9 @@ import com.android.ddmlib.SyncService.SyncResult;
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.debug.launching.DeviceChooserDialog.DeviceChooserResponse;
|
||||
import com.android.ide.eclipse.adt.debug.ui.EmulatorConfigTab;
|
||||
import com.android.ide.eclipse.adt.debug.ui.SkinRepository;
|
||||
import com.android.ide.eclipse.adt.project.ProjectHelper;
|
||||
import com.android.ide.eclipse.common.project.AndroidManifestHelper;
|
||||
import com.android.sdklib.SdkConstants;
|
||||
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
@@ -102,6 +102,9 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
|
||||
/** Debuggable attribute of the manifest file. */
|
||||
Boolean mDebuggable = null;
|
||||
|
||||
/** Required ApiVersionNumber by the app. 0 means no requirements */
|
||||
int mRequiredApiVersionNumber = 0;
|
||||
|
||||
InstallRetryMode mRetryMode = InstallRetryMode.NEVER;
|
||||
|
||||
/**
|
||||
@@ -126,8 +129,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
|
||||
|
||||
/** Basic constructor with activity and package info. */
|
||||
public DelayedLaunchInfo(IProject project, String packageName, String activity,
|
||||
IFile pack, Boolean debuggable, int launchAction, AndroidLaunch launch,
|
||||
IProgressMonitor monitor) {
|
||||
IFile pack, Boolean debuggable, int requiredApiVersionNumber, int launchAction,
|
||||
AndroidLaunch launch, IProgressMonitor monitor) {
|
||||
mProject = project;
|
||||
mPackageName = packageName;
|
||||
mActivity = activity;
|
||||
@@ -136,6 +139,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
|
||||
mLaunch = launch;
|
||||
mMonitor = monitor;
|
||||
mDebuggable = debuggable;
|
||||
mRequiredApiVersionNumber = requiredApiVersionNumber;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,13 +260,10 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
|
||||
try {
|
||||
mSkin = config.getAttribute(LaunchConfigDelegate.ATTR_SKIN, mSkin);
|
||||
if (mSkin == null) {
|
||||
mSkin = SkinRepository.getInstance().checkSkin(
|
||||
LaunchConfigDelegate.DEFAULT_SKIN);
|
||||
} else {
|
||||
mSkin = SkinRepository.getInstance().checkSkin(mSkin);
|
||||
mSkin = SdkConstants.SKIN_DEFAULT;
|
||||
}
|
||||
} catch (CoreException e) {
|
||||
mSkin = SkinRepository.getInstance().checkSkin(LaunchConfigDelegate.DEFAULT_SKIN);
|
||||
mSkin = SdkConstants.SKIN_DEFAULT;
|
||||
}
|
||||
|
||||
int index = LaunchConfigDelegate.DEFAULT_SPEED;
|
||||
@@ -539,7 +540,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
|
||||
LaunchConfigDelegate.DEFAULT_DELAY);
|
||||
|
||||
// default skin
|
||||
wc.setAttribute(LaunchConfigDelegate.ATTR_SKIN, LaunchConfigDelegate.DEFAULT_SKIN);
|
||||
wc.setAttribute(LaunchConfigDelegate.ATTR_SKIN, SdkConstants.SKIN_DEFAULT);
|
||||
|
||||
// default wipe data mode
|
||||
wc.setAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA,
|
||||
@@ -599,13 +600,14 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
|
||||
* defined by <code>ILaunchManager</code> - <code>RUN_MODE</code> or
|
||||
* <code>DEBUG_MODE</code>.
|
||||
* @param apk the resource to the apk to launch.
|
||||
* @param debuggable
|
||||
* @param debuggable the debuggable value of the app, or null if not set.
|
||||
* @param requiredApiVersionNumber the api version required by the app, or -1 if none.
|
||||
* @param activity the class to provide to am to launch
|
||||
* @param config the launch configuration
|
||||
* @param launch the launch object
|
||||
*/
|
||||
public void launch(final IProject project, String mode, IFile apk,
|
||||
String packageName, Boolean debuggable, String activity,
|
||||
String packageName, Boolean debuggable, int requiredApiVersionNumber, String activity,
|
||||
final AndroidLaunchConfiguration config, final AndroidLaunch launch,
|
||||
IProgressMonitor monitor) {
|
||||
|
||||
@@ -619,7 +621,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
|
||||
|
||||
// create the launch info
|
||||
final DelayedLaunchInfo launchInfo = new DelayedLaunchInfo(project, packageName,
|
||||
activity, apk, debuggable, config.mLaunchAction, launch, monitor);
|
||||
activity, apk, debuggable, requiredApiVersionNumber, config.mLaunchAction,
|
||||
launch, monitor);
|
||||
|
||||
// set the debug mode
|
||||
launchInfo.mDebugMode = mode.equals(ILaunchManager.DEBUG_MODE);
|
||||
@@ -711,6 +714,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
|
||||
|
||||
// stop the launch and return
|
||||
mWaitingForEmulatorLaunches.remove(launchInfo);
|
||||
AdtPlugin.printErrorToConsole(project, "Launch canceled!");
|
||||
launch.stopLaunch();
|
||||
return;
|
||||
}
|
||||
@@ -761,31 +765,63 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
|
||||
}
|
||||
}
|
||||
|
||||
private void checkBuildInfo(DelayedLaunchInfo launchInfo, Device device) {
|
||||
/**
|
||||
* Checks the build information, and returns whether the launch should continue.
|
||||
* <p/>The value tested are:
|
||||
* <ul>
|
||||
* <li>Minimum API version requested by the application. If the target device does not match,
|
||||
* the launch is canceled.</li>
|
||||
* <li>Debuggable attribute of the application and whether or not the device requires it. If
|
||||
* the device requires it and it is not set in the manifest, the launch will be forced to
|
||||
* "release" mode instead of "debug"</li>
|
||||
* <ul>
|
||||
* @param launchInfo
|
||||
* @param device
|
||||
* @return
|
||||
*/
|
||||
private boolean checkBuildInfo(DelayedLaunchInfo launchInfo, Device device) {
|
||||
if (device != null) {
|
||||
// get the SDK build
|
||||
String sdkBuild = AdtPlugin.getSdkApiVersion();
|
||||
|
||||
// can only complain if the sdkBuild is known
|
||||
if (sdkBuild != null) {
|
||||
|
||||
String deviceVersion = device.getProperty(Device.PROP_BUILD_VERSION);
|
||||
|
||||
if (deviceVersion == null) {
|
||||
AdtPlugin.printToConsole(launchInfo.mProject, "WARNING: Unknown device API version!");
|
||||
} else {
|
||||
if (sdkBuild.equals(deviceVersion) == false) {
|
||||
// TODO do a proper check, including testing the content of the uses-sdk string in the manifest to detect real incompatibility.
|
||||
String msg = String.format(
|
||||
"WARNING: Device API version (%1$s) does not match SDK API version (%2$s)",
|
||||
deviceVersion, sdkBuild);
|
||||
AdtPlugin.printErrorToConsole(launchInfo.mProject, msg);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
AdtPlugin.printToConsole(launchInfo.mProject, "WARNING: Unknown SDK API version!");
|
||||
// check the app required API level versus the target device API level
|
||||
|
||||
String deviceApiVersionName = device.getProperty(Device.PROP_BUILD_VERSION);
|
||||
String value = device.getProperty(Device.PROP_BUILD_VERSION_NUMBER);
|
||||
int deviceApiVersionNumber = 0;
|
||||
try {
|
||||
deviceApiVersionNumber = Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) {
|
||||
// pass, we'll keep the deviceVersionNumber value at 0.
|
||||
}
|
||||
|
||||
if (launchInfo.mRequiredApiVersionNumber == 0) {
|
||||
// warn the API level requirement is not set.
|
||||
AdtPlugin.printErrorToConsole(launchInfo.mProject,
|
||||
"WARNING: Application does not specify an API level requirement!");
|
||||
|
||||
// and display the target device API level (if known)
|
||||
if (deviceApiVersionName == null || deviceApiVersionNumber == 0) {
|
||||
AdtPlugin.printErrorToConsole(launchInfo.mProject,
|
||||
"WARNING: Unknown device API version!");
|
||||
} else {
|
||||
AdtPlugin.printErrorToConsole(launchInfo.mProject, String.format(
|
||||
"Device API version is %1$d (Android %2$s)", deviceApiVersionNumber,
|
||||
deviceApiVersionName));
|
||||
}
|
||||
} else { // app requires a specific API level
|
||||
if (deviceApiVersionName == null || deviceApiVersionNumber == 0) {
|
||||
AdtPlugin.printToConsole(launchInfo.mProject,
|
||||
"WARNING: Unknown device API version!");
|
||||
} else if (deviceApiVersionNumber < launchInfo.mRequiredApiVersionNumber) {
|
||||
String msg = String.format(
|
||||
"ERROR: Application requires API version %1$d. Device API version is %2$d (Android %3$s).",
|
||||
launchInfo.mRequiredApiVersionNumber, deviceApiVersionNumber,
|
||||
deviceApiVersionName);
|
||||
AdtPlugin.printErrorToConsole(launchInfo.mProject, msg);
|
||||
|
||||
// abort the launch
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// now checks that the device/app can be debugged (if needed)
|
||||
if (device.isEmulator() == false && launchInfo.mDebugMode) {
|
||||
String debuggableDevice = device.getProperty(Device.PROP_DEBUGGABLE);
|
||||
@@ -818,6 +854,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -829,10 +867,16 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
|
||||
* @return true if succeed
|
||||
*/
|
||||
private boolean simpleLaunch(DelayedLaunchInfo launchInfo, Device device) {
|
||||
checkBuildInfo(launchInfo, device);
|
||||
// API level check
|
||||
if (checkBuildInfo(launchInfo, device) == false) {
|
||||
AdtPlugin.printErrorToConsole(launchInfo.mProject, "Launch canceled!");
|
||||
launchInfo.mLaunch.stopLaunch();
|
||||
return false;
|
||||
}
|
||||
|
||||
// sync the app
|
||||
if (syncApp(launchInfo, device) == false) {
|
||||
AdtPlugin.printErrorToConsole(launchInfo.mProject, "Launch canceled!");
|
||||
launchInfo.mLaunch.stopLaunch();
|
||||
return false;
|
||||
}
|
||||
@@ -1127,6 +1171,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
|
||||
} catch (IOException e) {
|
||||
// something went wrong trying to launch the app.
|
||||
// lets stop the Launch
|
||||
AdtPlugin.printErrorToConsole(info.mProject,
|
||||
String.format("Launch error: %s", e.getMessage()));
|
||||
info.mLaunch.stopLaunch();
|
||||
|
||||
// and remove it from the list of app waiting for debuggers
|
||||
@@ -1139,25 +1185,20 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
|
||||
private boolean launchEmulator(AndroidLaunchConfiguration config) {
|
||||
|
||||
// split the custom command line in segments
|
||||
String[] segs;
|
||||
ArrayList<String> customArgs = new ArrayList<String>();
|
||||
boolean has_wipe_data = false;
|
||||
if (config.mEmulatorCommandLine != null && config.mEmulatorCommandLine.length() > 0) {
|
||||
segs = config.mEmulatorCommandLine.split("\\s+"); //$NON-NLS-1$
|
||||
String[] segments = config.mEmulatorCommandLine.split("\\s+"); //$NON-NLS-1$
|
||||
|
||||
// we need to remove the empty strings
|
||||
ArrayList<String> array = new ArrayList<String>();
|
||||
for (String s : segs) {
|
||||
for (String s : segments) {
|
||||
if (s.length() > 0) {
|
||||
array.add(s);
|
||||
customArgs.add(s);
|
||||
if (!has_wipe_data && s.equals(FLAG_WIPE_DATA)) {
|
||||
has_wipe_data = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
segs = array.toArray(new String[array.size()]);
|
||||
} else {
|
||||
segs = new String[0];
|
||||
}
|
||||
|
||||
boolean needs_wipe_data = config.mWipeData && !has_wipe_data;
|
||||
@@ -1167,30 +1208,38 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
|
||||
}
|
||||
}
|
||||
|
||||
boolean needs_no_boot_anim = config.mNoBootAnim;
|
||||
// build the command line based on the available parameters.
|
||||
ArrayList<String> list = new ArrayList<String>();
|
||||
|
||||
list.add(AdtPlugin.getOsAbsoluteEmulator());
|
||||
if (config.mSkin != null) {
|
||||
list.add(FLAG_SKIN);
|
||||
list.add(config.mSkin);
|
||||
}
|
||||
|
||||
if (config.mNetworkSpeed != null) {
|
||||
list.add(FLAG_NETSPEED);
|
||||
list.add(config.mNetworkSpeed);
|
||||
}
|
||||
|
||||
if (config.mNetworkDelay != null) {
|
||||
list.add(FLAG_NETDELAY);
|
||||
list.add(config.mNetworkDelay);
|
||||
}
|
||||
|
||||
// get the command line
|
||||
String[] command = new String[7 + segs.length +
|
||||
(needs_wipe_data ? 1 : 0) +
|
||||
(needs_no_boot_anim ? 1 : 0)];
|
||||
int index = 0;
|
||||
command[index++] = AdtPlugin.getOsAbsoluteEmulator();
|
||||
command[index++] = FLAG_SKIN; //$NON-NLS-1$
|
||||
command[index++] = config.mSkin;
|
||||
command[index++] = FLAG_NETSPEED; //$NON-NLS-1$
|
||||
command[index++] = config.mNetworkSpeed;
|
||||
command[index++] = FLAG_NETDELAY; //$NON-NLS-1$
|
||||
command[index++] = config.mNetworkDelay;
|
||||
if (needs_wipe_data) {
|
||||
command[index++] = FLAG_WIPE_DATA;
|
||||
list.add(FLAG_WIPE_DATA);
|
||||
}
|
||||
if (needs_no_boot_anim) {
|
||||
command[index++] = FLAG_NO_BOOT_ANIM;
|
||||
}
|
||||
for (String s : segs) {
|
||||
command[index++] = s;
|
||||
|
||||
if (config.mNoBootAnim) {
|
||||
list.add(FLAG_NO_BOOT_ANIM);
|
||||
}
|
||||
|
||||
list.addAll(customArgs);
|
||||
|
||||
// convert the list into an array for the call to exec.
|
||||
String[] command = list.toArray(new String[list.size()]);
|
||||
|
||||
// launch the emulator
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec(command);
|
||||
@@ -1278,12 +1327,11 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
|
||||
/**
|
||||
* Launch a new thread that connects a remote debugger on the specified port.
|
||||
* @param debugPort The port to connect the debugger to
|
||||
* @param launch The associated AndroidLaunch object.
|
||||
* @param androidLaunch The associated AndroidLaunch object.
|
||||
* @param monitor A Progress monitor
|
||||
* @see connectRemoveDebugger()
|
||||
*/
|
||||
public static void launchRemoteDebugger(final ILaunchConfiguration config,
|
||||
final int debugPort, final AndroidLaunch androidLaunch,
|
||||
public static void launchRemoteDebugger( final int debugPort, final AndroidLaunch androidLaunch,
|
||||
final IProgressMonitor monitor) {
|
||||
new Thread("Debugger connection") { //$NON-NLS-1$
|
||||
@Override
|
||||
@@ -1342,13 +1390,14 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
|
||||
synchronized (sListLock) {
|
||||
// look if there's an app waiting for a device
|
||||
if (mWaitingForEmulatorLaunches.size() > 0) {
|
||||
// remove first item from the list
|
||||
// get/remove first launch item from the list
|
||||
// FIXME: what if we have multiple launches waiting?
|
||||
DelayedLaunchInfo launchInfo = mWaitingForEmulatorLaunches.get(0);
|
||||
mWaitingForEmulatorLaunches.remove(0);
|
||||
|
||||
// give it its device
|
||||
|
||||
// give the launch item its device for later use.
|
||||
launchInfo.mDevice = device;
|
||||
|
||||
|
||||
// and move it to the other list
|
||||
mWaitingForReadyEmulatorList.add(launchInfo);
|
||||
|
||||
@@ -1433,12 +1482,23 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
|
||||
|
||||
// look for application waiting for home
|
||||
synchronized (sListLock) {
|
||||
boolean foundMatch = false;
|
||||
for (int i = 0 ; i < mWaitingForReadyEmulatorList.size() ;) {
|
||||
DelayedLaunchInfo launchInfo = mWaitingForReadyEmulatorList.get(i);
|
||||
if (launchInfo.mDevice == device) {
|
||||
// it's match, remove from the list
|
||||
mWaitingForReadyEmulatorList.remove(i);
|
||||
|
||||
// We couldn't check earlier the API level of the device
|
||||
// (it's asynchronous when the device boot, and usually
|
||||
// deviceConnected is called before it's queried for its build info)
|
||||
// so we check now
|
||||
if (checkBuildInfo(launchInfo, device) == false) {
|
||||
// device is not the proper API!
|
||||
AdtPlugin.printErrorToConsole(launchInfo.mProject,
|
||||
"Launch canceled!");
|
||||
launchInfo.mLaunch.stopLaunch();
|
||||
return;
|
||||
}
|
||||
|
||||
AdtPlugin.printToConsole(launchInfo.mProject,
|
||||
String.format("HOME is up on device '%1$s'",
|
||||
@@ -1448,16 +1508,14 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
|
||||
if (syncApp(launchInfo, device)) {
|
||||
// application package is sync'ed, lets attempt to launch it.
|
||||
launchApp(launchInfo, device);
|
||||
|
||||
// if we haven't checked the device build info, lets do it here
|
||||
if (foundMatch == false) {
|
||||
foundMatch = true;
|
||||
checkBuildInfo(launchInfo, device);
|
||||
}
|
||||
} else {
|
||||
// failure! Cancel and return
|
||||
AdtPlugin.printErrorToConsole(launchInfo.mProject,
|
||||
"Launch canceled!");
|
||||
launchInfo.mLaunch.stopLaunch();
|
||||
}
|
||||
|
||||
break;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
@@ -1530,6 +1588,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
|
||||
}
|
||||
} catch (CoreException e) {
|
||||
// well something went wrong.
|
||||
AdtPlugin.printErrorToConsole(launchInfo.mProject,
|
||||
String.format("Launch error: %s", e.getMessage()));
|
||||
// stop the launch
|
||||
launchInfo.mLaunch.stopLaunch();
|
||||
}
|
||||
|
||||
@@ -83,11 +83,6 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate {
|
||||
/** Skin to be used to launch the emulator */
|
||||
public static final String ATTR_SKIN = AdtPlugin.PLUGIN_ID + ".skin"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Name of the default Skin.
|
||||
*/
|
||||
public static final String DEFAULT_SKIN = "HVGA"; //$NON-NLS-1$
|
||||
|
||||
public static final String ATTR_SPEED = AdtPlugin.PLUGIN_ID + ".speed"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
@@ -138,8 +133,7 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate {
|
||||
// if we have a valid debug port, this means we're debugging an app
|
||||
// that's already launched.
|
||||
if (debugPort != INVALID_DEBUG_PORT) {
|
||||
AndroidLaunchController.launchRemoteDebugger(configuration,
|
||||
debugPort, androidLaunch, monitor);
|
||||
AndroidLaunchController.launchRemoteDebugger(debugPort, androidLaunch, monitor);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -302,7 +296,8 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate {
|
||||
// everything seems fine, we ask the launch controller to handle
|
||||
// the rest
|
||||
controller.launch(project, mode, applicationPackage, manifestParser.getPackage(),
|
||||
manifestParser.getDebuggable(), activityName, config, androidLaunch, monitor);
|
||||
manifestParser.getDebuggable(), manifestParser.getApiLevelRequirement(),
|
||||
activityName, config, androidLaunch, monitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,13 +18,19 @@ package com.android.ide.eclipse.adt.debug.ui;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.debug.launching.LaunchConfigDelegate;
|
||||
import com.android.ide.eclipse.adt.debug.ui.SkinRepository.Skin;
|
||||
import com.android.ide.eclipse.adt.sdk.Sdk;
|
||||
import com.android.ide.eclipse.common.project.BaseProjectHelper;
|
||||
import com.android.ide.eclipse.ddms.DdmsPlugin;
|
||||
import com.android.sdklib.IAndroidTarget;
|
||||
import com.android.sdklib.SdkConstants;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.debug.core.ILaunchConfiguration;
|
||||
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
|
||||
import org.eclipse.debug.ui.AbstractLaunchConfigurationTab;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
|
||||
import org.eclipse.jface.preference.IPreferenceStore;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.ModifyEvent;
|
||||
@@ -82,6 +88,8 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
|
||||
|
||||
private Button mNoBootAnimButton;
|
||||
|
||||
private IAndroidTarget mTarget;
|
||||
|
||||
/**
|
||||
* Returns the emulator ready speed option value.
|
||||
* @param value The index of the combo selection.
|
||||
@@ -169,12 +177,6 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
|
||||
new Label(mEmulatorOptionsGroup, SWT.NONE).setText("Screen Size:");
|
||||
|
||||
mSkinCombo = new Combo(mEmulatorOptionsGroup, SWT.READ_ONLY);
|
||||
Skin[] skins = SkinRepository.getInstance().getSkins();
|
||||
if (skins != null) {
|
||||
for (Skin skin : skins) {
|
||||
mSkinCombo.add(skin.getDescription());
|
||||
}
|
||||
}
|
||||
mSkinCombo.addSelectionListener(new SelectionAdapter() {
|
||||
// called when selection changes
|
||||
@Override
|
||||
@@ -182,7 +184,6 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
|
||||
updateLaunchConfigurationDialog();
|
||||
}
|
||||
});
|
||||
mSkinCombo.pack();
|
||||
|
||||
// network options
|
||||
new Label(mEmulatorOptionsGroup, SWT.NONE).setText("Network Speed:");
|
||||
@@ -287,6 +288,42 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
|
||||
}
|
||||
mAutoTargetButton.setSelection(value);
|
||||
mManualTargetButton.setSelection(!value);
|
||||
|
||||
// look for the project name to get its target.
|
||||
String projectName = "";
|
||||
try {
|
||||
projectName = configuration.getAttribute(
|
||||
IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, projectName);
|
||||
} catch (CoreException ce) {
|
||||
}
|
||||
|
||||
IProject project = null;
|
||||
|
||||
// get the list of existing Android projects from the workspace.
|
||||
IJavaProject[] projects = BaseProjectHelper.getAndroidProjects();
|
||||
if (projects != null) {
|
||||
// look for the project whose name we read from the configuration.
|
||||
for (IJavaProject p : projects) {
|
||||
if (p.getElementName().equals(projectName)) {
|
||||
project = p.getProject();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mSkinCombo.removeAll();
|
||||
if (project != null) {
|
||||
mTarget = Sdk.getCurrent().getTarget(project);
|
||||
if (mTarget != null) {
|
||||
String[] skins = mTarget.getSkins();
|
||||
if (skins != null) {
|
||||
for (String skin : skins) {
|
||||
mSkinCombo.add(skin);
|
||||
}
|
||||
mSkinCombo.pack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value = LaunchConfigDelegate.DEFAULT_WIPE_DATA;
|
||||
try {
|
||||
@@ -307,16 +344,18 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
|
||||
int index = -1;
|
||||
try {
|
||||
String skin = configuration.getAttribute(LaunchConfigDelegate.ATTR_SKIN, (String)null);
|
||||
if (skin != null) {
|
||||
index = getSkinIndex(skin);
|
||||
if (skin == null) {
|
||||
skin = SdkConstants.SKIN_DEFAULT;
|
||||
}
|
||||
|
||||
index = getSkinIndex(skin);
|
||||
} catch (CoreException e) {
|
||||
index = getSkinIndex(SkinRepository.getInstance().checkSkin(
|
||||
LaunchConfigDelegate.DEFAULT_SKIN));
|
||||
index = getSkinIndex(SdkConstants.SKIN_DEFAULT);
|
||||
}
|
||||
|
||||
if (index == -1) {
|
||||
mSkinCombo.clearSelection();
|
||||
mSkinCombo.select(0);
|
||||
updateLaunchConfigurationDialog();
|
||||
} else {
|
||||
mSkinCombo.select(index);
|
||||
}
|
||||
@@ -387,7 +426,7 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
|
||||
configuration.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE,
|
||||
LaunchConfigDelegate.DEFAULT_TARGET_MODE);
|
||||
configuration.setAttribute(LaunchConfigDelegate.ATTR_SKIN,
|
||||
LaunchConfigDelegate.DEFAULT_SKIN);
|
||||
SdkConstants.SKIN_DEFAULT);
|
||||
configuration.setAttribute(LaunchConfigDelegate.ATTR_SPEED,
|
||||
LaunchConfigDelegate.DEFAULT_SPEED);
|
||||
configuration.setAttribute(LaunchConfigDelegate.ATTR_DELAY,
|
||||
@@ -403,11 +442,31 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
|
||||
}
|
||||
|
||||
private String getSkinNameByIndex(int index) {
|
||||
return SkinRepository.getInstance().getSkinNameByIndex(index);
|
||||
if (mTarget != null && index > 0) {
|
||||
String[] skins = mTarget.getSkins();
|
||||
if (skins != null && index < skins.length) {
|
||||
return skins[index];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private int getSkinIndex(String name) {
|
||||
return SkinRepository.getInstance().getSkinIndex(name);
|
||||
if (mTarget != null) {
|
||||
String[] skins = mTarget.getSkins();
|
||||
if (skins != null) {
|
||||
int index = 0;
|
||||
for (String skin : skins) {
|
||||
if (skin.equalsIgnoreCase(name)) {
|
||||
return index;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.debug.ui;
|
||||
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Repository for the emulator skins. This class is responsible for parsing the skin folder.
|
||||
*/
|
||||
public class SkinRepository {
|
||||
|
||||
private final static SkinRepository sInstance = new SkinRepository();
|
||||
|
||||
private Skin[] mSkins;
|
||||
|
||||
public static class Skin {
|
||||
|
||||
String mName;
|
||||
|
||||
public Skin(String name) {
|
||||
mName = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the human readable description of the skin.
|
||||
*/
|
||||
public String getDescription() {
|
||||
// TODO: parse the skin and output the description.
|
||||
return mName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the singleton instance.
|
||||
*/
|
||||
public static SkinRepository getInstance() {
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the skin folder and build the skin list.
|
||||
* @param osPath The path of the skin folder.
|
||||
*/
|
||||
public void parseFolder(String osPath) {
|
||||
File skinFolder = new File(osPath);
|
||||
|
||||
if (skinFolder.isDirectory()) {
|
||||
ArrayList<Skin> skinList = new ArrayList<Skin>();
|
||||
|
||||
File[] files = skinFolder.listFiles();
|
||||
|
||||
for (File skin : files) {
|
||||
if (skin.isDirectory()) {
|
||||
// check for layout file
|
||||
File layout = new File(skin.getPath() + File.separator
|
||||
+ AndroidConstants.FN_LAYOUT);
|
||||
|
||||
if (layout.isFile()) {
|
||||
// for now we don't parse the content of the layout and
|
||||
// simply add the directory to the list.
|
||||
skinList.add(new Skin(skin.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mSkins = skinList.toArray(new Skin[skinList.size()]);
|
||||
} else {
|
||||
mSkins = new Skin[0];
|
||||
}
|
||||
}
|
||||
|
||||
public Skin[] getSkins() {
|
||||
return mSkins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a valid skin folder name. If <code>skin</code> is valid, then it is returned,
|
||||
* otherwise the first valid skin name is returned.
|
||||
* @param skin the Skin name to check
|
||||
* @return a valid skin name or null if there aren't any.
|
||||
*/
|
||||
public String checkSkin(String skin) {
|
||||
if (mSkins != null) {
|
||||
for (Skin s : mSkins) {
|
||||
if (s.getName().equals(skin)) {
|
||||
return skin;
|
||||
}
|
||||
}
|
||||
|
||||
if (mSkins.length > 0) {
|
||||
return mSkins[0].getName();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the name of a skin by index.
|
||||
* @param index The index of the skin to return
|
||||
* @return the skin name of null if the index is invalid.
|
||||
*/
|
||||
public String getSkinNameByIndex(int index) {
|
||||
if (mSkins != null) {
|
||||
if (index >= 0 && index < mSkins.length) {
|
||||
return mSkins[index].getName();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index (0 based) of the skin matching the name.
|
||||
* @param name The name of the skin to look for.
|
||||
* @return the index of the skin or -1 if the skin was not found.
|
||||
*/
|
||||
public int getSkinIndex(String name) {
|
||||
if (mSkins != null) {
|
||||
int count = mSkins.length;
|
||||
for (int i = 0 ; i < count ; i++) {
|
||||
Skin s = mSkins[i];
|
||||
if (s.mName.equals(name)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.editors.java;
|
||||
|
||||
import org.eclipse.jdt.ui.PreferenceConstants;
|
||||
import org.eclipse.jdt.ui.text.IJavaPartitions;
|
||||
import org.eclipse.jdt.ui.text.JavaSourceViewerConfiguration;
|
||||
import org.eclipse.jdt.ui.text.JavaTextTools;
|
||||
import org.eclipse.jface.preference.IPreferenceStore;
|
||||
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditor;
|
||||
|
||||
/**
|
||||
* Read only java editors. This looks like the regular Java editor, except that it
|
||||
* prevents editing the files. This is used for automatically generated java classes.
|
||||
*/
|
||||
public class ReadOnlyJavaEditor extends AbstractDecoratedTextEditor {
|
||||
|
||||
public final static String ID =
|
||||
"com.android.ide.eclipse.adt.editors.java.ReadOnlyJavaEditor"; //$NON-NLS-1$
|
||||
|
||||
public ReadOnlyJavaEditor() {
|
||||
IPreferenceStore javaUiStore = PreferenceConstants.getPreferenceStore();
|
||||
JavaTextTools jtt = new JavaTextTools(javaUiStore);
|
||||
|
||||
JavaSourceViewerConfiguration jsvc = new JavaSourceViewerConfiguration(
|
||||
jtt.getColorManager(), javaUiStore, this, IJavaPartitions.JAVA_PARTITIONING);
|
||||
|
||||
setSourceViewerConfiguration(jsvc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEditable() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,9 @@ package com.android.ide.eclipse.adt.preferences;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtConstants;
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.jarutils.DebugKeyProvider;
|
||||
import com.android.jarutils.DebugKeyProvider.KeytoolException;
|
||||
import com.android.prefs.AndroidLocation.AndroidLocationException;
|
||||
|
||||
import org.eclipse.jface.preference.BooleanFieldEditor;
|
||||
import org.eclipse.jface.preference.FieldEditorPreferencePage;
|
||||
@@ -29,6 +32,13 @@ import org.eclipse.swt.widgets.Text;
|
||||
import org.eclipse.ui.IWorkbench;
|
||||
import org.eclipse.ui.IWorkbenchPreferencePage;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Preference page for build options.
|
||||
*
|
||||
@@ -75,7 +85,7 @@ public class BuildPreferencePage extends FieldEditorPreferencePage implements
|
||||
addField(new ReadOnlyFieldEditor(AdtPlugin.PREFS_DEFAULT_DEBUG_KEYSTORE,
|
||||
Messages.BuildPreferencePage_Default_KeyStore, getFieldEditorParent()));
|
||||
|
||||
addField(new FileFieldEditor(AdtPlugin.PREFS_CUSTOM_DEBUG_KEYSTORE,
|
||||
addField(new KeystoreFieldEditor(AdtPlugin.PREFS_CUSTOM_DEBUG_KEYSTORE,
|
||||
Messages.BuildPreferencePage_Custom_Keystore, getFieldEditorParent()));
|
||||
|
||||
}
|
||||
@@ -105,4 +115,103 @@ public class BuildPreferencePage extends FieldEditorPreferencePage implements
|
||||
control.setEditable(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom {@link FileFieldEditor} that checks that the keystore is valid.
|
||||
*/
|
||||
private static class KeystoreFieldEditor extends FileFieldEditor {
|
||||
public KeystoreFieldEditor(String name, String label, Composite parent) {
|
||||
super(name, label, parent);
|
||||
setValidateStrategy(VALIDATE_ON_KEY_STROKE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkState() {
|
||||
String fileName = getTextControl().getText();
|
||||
fileName = fileName.trim();
|
||||
|
||||
// empty values are considered ok.
|
||||
if (fileName.length() > 0) {
|
||||
File file = new File(fileName);
|
||||
if (file.isFile()) {
|
||||
// attempt to load the debug key.
|
||||
try {
|
||||
DebugKeyProvider provider = new DebugKeyProvider(fileName,
|
||||
null /* storeType */, null /* key gen output */);
|
||||
PrivateKey key = provider.getDebugKey();
|
||||
X509Certificate certificate = (X509Certificate)provider.getCertificate();
|
||||
|
||||
if (key == null || certificate == null) {
|
||||
showErrorMessage("Unable to find debug key in keystore!");
|
||||
return false;
|
||||
}
|
||||
|
||||
Date today = new Date();
|
||||
if (certificate.getNotAfter().compareTo(today) < 0) {
|
||||
showErrorMessage("Certificate is expired!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (certificate.getNotBefore().compareTo(today) > 0) {
|
||||
showErrorMessage("Certificate validity is in the future!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// we're good!
|
||||
clearErrorMessage();
|
||||
return true;
|
||||
} catch (GeneralSecurityException e) {
|
||||
handleException(e);
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
handleException(e);
|
||||
return false;
|
||||
} catch (KeytoolException e) {
|
||||
handleException(e);
|
||||
return false;
|
||||
} catch (AndroidLocationException e) {
|
||||
handleException(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
// file does not exist.
|
||||
showErrorMessage("Not a valid keystore path.");
|
||||
return false; // Apply/OK must be disabled
|
||||
}
|
||||
}
|
||||
|
||||
clearErrorMessage();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Text getTextControl(Composite parent) {
|
||||
setValidateStrategy(VALIDATE_ON_KEY_STROKE);
|
||||
return super.getTextControl(parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the error message from a {@link Throwable}. If the exception has no message, try
|
||||
* to get the message from the cause.
|
||||
* @param t the Throwable.
|
||||
*/
|
||||
private void handleException(Throwable t) {
|
||||
String msg = t.getMessage();
|
||||
if (t == null) {
|
||||
Throwable cause = t.getCause();
|
||||
if (cause != null) {
|
||||
handleException(cause);
|
||||
} else {
|
||||
setErrorMessage("Uknown error when getting the debug key!");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// valid text, display it.
|
||||
showErrorMessage(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.ide.eclipse.adt.project;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
@@ -117,7 +118,7 @@ public class CreateAidlImportAction implements IObjectActionDelegate {
|
||||
// create the file with the parcelables
|
||||
if (parcelables.size() > 0) {
|
||||
IPath path = project.getLocation();
|
||||
path = path.append("/project.aidl"); //$NON-NLS-1$
|
||||
path = path.append(AndroidConstants.FN_PROJECT_AIDL);
|
||||
|
||||
File f = new File(path.toOSString());
|
||||
if (f.exists() == false) {
|
||||
@@ -194,7 +195,7 @@ public class CreateAidlImportAction implements IObjectActionDelegate {
|
||||
|
||||
IType[] superInterfaces = typeHierarchy.getAllSuperInterfaces(type);
|
||||
for (IType superInterface : superInterfaces) {
|
||||
if ("android.os.Parcelable".equals(superInterface.getFullyQualifiedName())) { //$NON-NLS-1$
|
||||
if (AndroidConstants.CLASS_PARCELABLE.equals(superInterface.getFullyQualifiedName())) {
|
||||
parcelables.add(type.getFullyQualifiedName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.project;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
|
||||
import org.eclipse.core.resources.IFolder;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.jface.resource.ImageDescriptor;
|
||||
import org.eclipse.jface.viewers.IDecoration;
|
||||
import org.eclipse.jface.viewers.ILabelDecorator;
|
||||
import org.eclipse.jface.viewers.ILabelProviderListener;
|
||||
import org.eclipse.jface.viewers.ILightweightLabelDecorator;
|
||||
|
||||
/**
|
||||
* A {@link ILabelDecorator} associated with an org.eclipse.ui.decorators extension.
|
||||
* This is used to add android icons in some special folders in the package explorer.
|
||||
*/
|
||||
public class FolderDecorator implements ILightweightLabelDecorator {
|
||||
|
||||
private ImageDescriptor mDescriptor;
|
||||
|
||||
public FolderDecorator() {
|
||||
mDescriptor = AdtPlugin.getImageDescriptor("/icons/android_project.png");
|
||||
}
|
||||
|
||||
public void decorate(Object element, IDecoration decoration) {
|
||||
if (element instanceof IFolder) {
|
||||
IFolder folder = (IFolder)element;
|
||||
|
||||
// get the project and make sure this is an android project
|
||||
IProject project = folder.getProject();
|
||||
|
||||
try {
|
||||
if (project.hasNature(AndroidConstants.NATURE)) {
|
||||
// check the folder is directly under the project.
|
||||
if (folder.getParent().getType() == IResource.PROJECT) {
|
||||
String name = folder.getName();
|
||||
if (name.equals(AndroidConstants.FD_ASSETS)) {
|
||||
decorate(decoration, " [Android assets]");
|
||||
decoration.addOverlay(mDescriptor, IDecoration.TOP_RIGHT);
|
||||
} else if (name.equals(AndroidConstants.FD_RESOURCES)) {
|
||||
decorate(decoration, " [Android resources]");
|
||||
decoration.addOverlay(mDescriptor, IDecoration.TOP_RIGHT);
|
||||
} else if (name.equals(AndroidConstants.FD_NATIVE_LIBS)) {
|
||||
decorate(decoration, " [Native Libraries]");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (CoreException e) {
|
||||
// log the error
|
||||
AdtPlugin.log(e, "Unable to get nature of project '%s'.", project.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void decorate(IDecoration decoration, String suffix) {
|
||||
decoration.addOverlay(mDescriptor, IDecoration.TOP_RIGHT);
|
||||
|
||||
// this is broken as it changes the color of the whole text, not only of the decoration.
|
||||
// TODO: figure out how to change the color of the decoration only.
|
||||
// decoration.addSuffix(suffix);
|
||||
// ITheme theme = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme();
|
||||
// ColorRegistry registry = theme.getColorRegistry();
|
||||
// decoration.setForegroundColor(registry.get("org.eclipse.jdt.ui.ColoredLabels.decorations"));
|
||||
|
||||
}
|
||||
|
||||
public boolean isLabelProperty(Object element, String property) {
|
||||
// at this time return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
public void addListener(ILabelProviderListener listener) {
|
||||
// No state change will affect the rendering.
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void removeListener(ILabelProviderListener listener) {
|
||||
// No state change will affect the rendering.
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
// nothind to dispose
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.project;
|
||||
|
||||
import com.android.ide.eclipse.editors.wizards.NewXmlFileWizard;
|
||||
|
||||
import org.eclipse.jface.action.IAction;
|
||||
import org.eclipse.jface.viewers.ISelection;
|
||||
import org.eclipse.jface.viewers.IStructuredSelection;
|
||||
import org.eclipse.jface.wizard.WizardDialog;
|
||||
import org.eclipse.ui.IObjectActionDelegate;
|
||||
import org.eclipse.ui.IWorkbench;
|
||||
import org.eclipse.ui.IWorkbenchPart;
|
||||
|
||||
public class NewXmlFileWizardAction implements IObjectActionDelegate {
|
||||
|
||||
private ISelection mSelection;
|
||||
private IWorkbench mWorkbench;
|
||||
|
||||
/**
|
||||
* @see IObjectActionDelegate#setActivePart(IAction, IWorkbenchPart)
|
||||
*/
|
||||
public void setActivePart(IAction action, IWorkbenchPart targetPart) {
|
||||
mWorkbench = targetPart.getSite().getWorkbenchWindow().getWorkbench();
|
||||
}
|
||||
|
||||
public void run(IAction action) {
|
||||
if (mSelection instanceof IStructuredSelection) {
|
||||
IStructuredSelection selection = (IStructuredSelection)mSelection;
|
||||
|
||||
// call the new xml file wizard on the current selection.
|
||||
NewXmlFileWizard wizard = new NewXmlFileWizard();
|
||||
wizard.init(mWorkbench, selection);
|
||||
WizardDialog dialog = new WizardDialog(mWorkbench.getDisplay().getActiveShell(),
|
||||
wizard);
|
||||
dialog.open();
|
||||
}
|
||||
}
|
||||
|
||||
public void selectionChanged(IAction action, ISelection selection) {
|
||||
this.mSelection = selection;
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,6 @@ import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.core.runtime.NullProgressMonitor;
|
||||
import org.eclipse.core.runtime.QualifiedName;
|
||||
import org.eclipse.jdt.core.IClasspathAttribute;
|
||||
import org.eclipse.jdt.core.IClasspathEntry;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jdt.core.JavaCore;
|
||||
@@ -177,31 +176,6 @@ public final class ProjectHelper {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the validity of the javadoc attributes in a classpath entry.
|
||||
* @param frameworkEntry the classpath entry to check.
|
||||
* @return true if the javadoc attributes is valid, false otherwise.
|
||||
*/
|
||||
public static boolean checkJavaDocPath(IClasspathEntry frameworkEntry) {
|
||||
// get the list of extra attributes
|
||||
IClasspathAttribute[] attributes = frameworkEntry.getExtraAttributes();
|
||||
|
||||
// and search for the one about the javadoc
|
||||
for (IClasspathAttribute att : attributes) {
|
||||
if (IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME.
|
||||
equals(att.getName())) {
|
||||
// we found a javadoc attribute. Now we test its value.
|
||||
// get the expect value
|
||||
String validJavaDoc = getJavaDocPath(AdtPlugin.getOsAbsoluteFramework());
|
||||
|
||||
// now compare and return
|
||||
return validJavaDoc.equals(att.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix the project. This checks the SDK location.
|
||||
* @param project The project to fix.
|
||||
@@ -264,14 +238,7 @@ public final class ProjectHelper {
|
||||
continue;
|
||||
}
|
||||
} else if (kind == IClasspathEntry.CPE_CONTAINER) {
|
||||
IPath containerPath = entry.getPath();
|
||||
|
||||
if (AndroidClasspathContainerInitializer.checkOldPath(containerPath)) {
|
||||
entries = ProjectHelper.removeEntryFromClasspath(entries, i);
|
||||
|
||||
// continue, to skip the i++;
|
||||
continue;
|
||||
} else if (AndroidClasspathContainerInitializer.checkPath(containerPath)) {
|
||||
if (AndroidClasspathContainerInitializer.checkPath(entry.getPath())) {
|
||||
foundContainer = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,10 @@ package com.android.ide.eclipse.adt.project.export;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.project.ProjectHelper;
|
||||
import com.android.jarutils.KeystoreHelper;
|
||||
import com.android.jarutils.SignedJarBuilder;
|
||||
import com.android.jarutils.DebugKeyProvider.IKeyGenOutput;
|
||||
import com.android.jarutils.DebugKeyProvider.KeytoolException;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
@@ -27,28 +30,38 @@ import org.eclipse.jface.resource.ImageDescriptor;
|
||||
import org.eclipse.jface.viewers.IStructuredSelection;
|
||||
import org.eclipse.jface.wizard.Wizard;
|
||||
import org.eclipse.jface.wizard.WizardPage;
|
||||
import org.eclipse.swt.events.VerifyEvent;
|
||||
import org.eclipse.swt.events.VerifyListener;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
import org.eclipse.ui.IExportWizard;
|
||||
import org.eclipse.ui.IWorkbench;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.KeyStore.PrivateKeyEntry;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Export wizard to export an apk signed with a release key/certificate.
|
||||
*/
|
||||
public class ExportWizard extends Wizard implements IExportWizard {
|
||||
public final class ExportWizard extends Wizard implements IExportWizard {
|
||||
|
||||
private static final String PROJECT_LOGO_LARGE = "icons/android_large.png"; //$NON-NLS-1$
|
||||
|
||||
private static final String PAGE_PRE = "preExportPage"; //$NON-NLS-1$
|
||||
private static final String PAGE_SIGNING = "signingExportPage"; //$NON-NLS-1$
|
||||
private static final String PAGE_FINAL = "finalExportPage"; //$NON-NLS-1$
|
||||
private static final String PAGE_PROJECT_CHECK = "Page_ProjectCheck"; //$NON-NLS-1$
|
||||
private static final String PAGE_KEYSTORE_SELECTION = "Page_KeystoreSelection"; //$NON-NLS-1$
|
||||
private static final String PAGE_KEY_CREATION = "Page_KeyCreation"; //$NON-NLS-1$
|
||||
private static final String PAGE_KEY_SELECTION = "Page_KeySelection"; //$NON-NLS-1$
|
||||
private static final String PAGE_KEY_CHECK = "Page_KeyCheck"; //$NON-NLS-1$
|
||||
|
||||
static final String PROPERTY_KEYSTORE = "keystore"; //$NON-NLS-1$
|
||||
static final String PROPERTY_ALIAS = "alias"; //$NON-NLS-1$
|
||||
@@ -59,7 +72,41 @@ public class ExportWizard extends Wizard implements IExportWizard {
|
||||
*/
|
||||
static abstract class ExportWizardPage extends WizardPage {
|
||||
|
||||
protected boolean mNewProjectReference = true;
|
||||
/** bit mask constant for project data change event */
|
||||
protected static final int DATA_PROJECT = 0x001;
|
||||
/** bit mask constant for keystore data change event */
|
||||
protected static final int DATA_KEYSTORE = 0x002;
|
||||
/** bit mask constant for key data change event */
|
||||
protected static final int DATA_KEY = 0x004;
|
||||
|
||||
protected static final VerifyListener sPasswordVerifier = new VerifyListener() {
|
||||
public void verifyText(VerifyEvent e) {
|
||||
// verify the characters are valid for password.
|
||||
int len = e.text.length();
|
||||
|
||||
// first limit to 127 characters max
|
||||
if (len + ((Text)e.getSource()).getText().length() > 127) {
|
||||
e.doit = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// now only take non control characters
|
||||
for (int i = 0 ; i < len ; i++) {
|
||||
if (e.text.charAt(i) < 32) {
|
||||
e.doit = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Bit mask indicating what changed while the page was hidden.
|
||||
* @see #DATA_PROJECT
|
||||
* @see #DATA_KEYSTORE
|
||||
* @see #DATA_KEY
|
||||
*/
|
||||
protected int mProjectDataChanged = 0;
|
||||
|
||||
ExportWizardPage(String name) {
|
||||
super(name);
|
||||
@@ -72,23 +119,38 @@ public class ExportWizard extends Wizard implements IExportWizard {
|
||||
super.setVisible(visible);
|
||||
if (visible) {
|
||||
onShow();
|
||||
mNewProjectReference = false;
|
||||
mProjectDataChanged = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void newProjectReference() {
|
||||
mNewProjectReference = true;
|
||||
final void projectDataChanged(int changeMask) {
|
||||
mProjectDataChanged |= changeMask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #setErrorMessage(String)} and {@link #setPageComplete(boolean)} based on a
|
||||
* {@link Throwable} object.
|
||||
*/
|
||||
protected final void onException(Throwable t) {
|
||||
String message = getExceptionMessage(t);
|
||||
|
||||
setErrorMessage(message);
|
||||
setPageComplete(false);
|
||||
}
|
||||
}
|
||||
|
||||
private ExportWizardPage mPages[] = new ExportWizardPage[3];
|
||||
private ExportWizardPage mPages[] = new ExportWizardPage[5];
|
||||
|
||||
private IProject mProject;
|
||||
|
||||
private String mKeystore;
|
||||
private String mKeystorePassword;
|
||||
private boolean mKeystoreCreationMode;
|
||||
|
||||
private String mKeyAlias;
|
||||
private char[] mKeystorePassword;
|
||||
private char[] mKeyPassword;
|
||||
private String mKeyPassword;
|
||||
private int mValidity;
|
||||
private String mDName;
|
||||
|
||||
private PrivateKey mPrivateKey;
|
||||
private X509Certificate mCertificate;
|
||||
@@ -97,6 +159,15 @@ public class ExportWizard extends Wizard implements IExportWizard {
|
||||
private String mApkFilePath;
|
||||
private String mApkFileName;
|
||||
|
||||
private ExportWizardPage mKeystoreSelectionPage;
|
||||
private ExportWizardPage mKeyCreationPage;
|
||||
private ExportWizardPage mKeySelectionPage;
|
||||
private ExportWizardPage mKeyCheckPage;
|
||||
|
||||
private boolean mKeyCreationMode;
|
||||
|
||||
private List<String> mExistingAliases;
|
||||
|
||||
public ExportWizard() {
|
||||
setHelpAvailable(false); // TODO have help
|
||||
setWindowTitle("Export Android Application");
|
||||
@@ -105,46 +176,101 @@ public class ExportWizard extends Wizard implements IExportWizard {
|
||||
|
||||
@Override
|
||||
public void addPages() {
|
||||
addPage(mPages[0] = new PreExportPage(this, PAGE_PRE));
|
||||
addPage(mPages[1] = new SigningExportPage(this, PAGE_SIGNING));
|
||||
addPage(mPages[2] = new FinalExportPage(this, PAGE_FINAL));
|
||||
addPage(mPages[0] = new ProjectCheckPage(this, PAGE_PROJECT_CHECK));
|
||||
addPage(mKeystoreSelectionPage = mPages[1] = new KeystoreSelectionPage(this,
|
||||
PAGE_KEYSTORE_SELECTION));
|
||||
addPage(mKeyCreationPage = mPages[2] = new KeyCreationPage(this, PAGE_KEY_CREATION));
|
||||
addPage(mKeySelectionPage = mPages[3] = new KeySelectionPage(this, PAGE_KEY_SELECTION));
|
||||
addPage(mKeyCheckPage = mPages[4] = new KeyCheckPage(this, PAGE_KEY_CHECK));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performFinish() {
|
||||
// first we make sure export is fine if the destination file already exists
|
||||
File f = new File(mDestinationPath);
|
||||
if (f.isFile()) {
|
||||
if (AdtPlugin.displayPrompt("Export Wizard",
|
||||
"File already exists. Do you want to overwrite it?") == false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// save the properties
|
||||
ProjectHelper.saveStringProperty(mProject, PROPERTY_KEYSTORE, mKeystore);
|
||||
ProjectHelper.saveStringProperty(mProject, PROPERTY_ALIAS, mKeyAlias);
|
||||
ProjectHelper.saveStringProperty(mProject, PROPERTY_DESTINATION, mDestinationPath);
|
||||
|
||||
try {
|
||||
FileOutputStream fos = new FileOutputStream(mDestinationPath);
|
||||
SignedJarBuilder builder = new SignedJarBuilder(fos, mPrivateKey, mCertificate);
|
||||
|
||||
// get the input file.
|
||||
FileInputStream fis = new FileInputStream(mApkFilePath);
|
||||
try {
|
||||
builder.writeZip(fis, null /* filter */);
|
||||
} finally {
|
||||
if (mKeystoreCreationMode || mKeyCreationMode) {
|
||||
final ArrayList<String> output = new ArrayList<String>();
|
||||
if (KeystoreHelper.createNewStore(
|
||||
mKeystore,
|
||||
null /*storeType*/,
|
||||
mKeystorePassword,
|
||||
mKeyAlias,
|
||||
mKeyPassword,
|
||||
mDName,
|
||||
mValidity,
|
||||
new IKeyGenOutput() {
|
||||
public void err(String message) {
|
||||
output.add(message);
|
||||
}
|
||||
public void out(String message) {
|
||||
output.add(message);
|
||||
}
|
||||
}) == false) {
|
||||
// keystore creation error!
|
||||
displayError(output.toArray(new String[output.size()]));
|
||||
return false;
|
||||
}
|
||||
|
||||
// keystore is created, now load the private key and certificate.
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
FileInputStream fis = new FileInputStream(mKeystore);
|
||||
keyStore.load(fis, mKeystorePassword.toCharArray());
|
||||
fis.close();
|
||||
PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(
|
||||
mKeyAlias, new KeyStore.PasswordProtection(mKeyPassword.toCharArray()));
|
||||
|
||||
if (entry != null) {
|
||||
mPrivateKey = entry.getPrivateKey();
|
||||
mCertificate = (X509Certificate)entry.getCertificate();
|
||||
} else {
|
||||
// this really shouldn't happen since we now let the user choose the key
|
||||
// from a list read from the store.
|
||||
displayError("Could not find key");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
builder.close();
|
||||
fos.close();
|
||||
|
||||
return true;
|
||||
// check the private key/certificate again since it may have been created just above.
|
||||
if (mPrivateKey != null && mCertificate != null) {
|
||||
FileOutputStream fos = new FileOutputStream(mDestinationPath);
|
||||
SignedJarBuilder builder = new SignedJarBuilder(fos, mPrivateKey, mCertificate);
|
||||
|
||||
// get the input file.
|
||||
FileInputStream fis = new FileInputStream(mApkFilePath);
|
||||
try {
|
||||
builder.writeZip(fis, null /* filter */);
|
||||
} finally {
|
||||
fis.close();
|
||||
}
|
||||
|
||||
builder.close();
|
||||
fos.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
displayError(e);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
displayError(e);
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
displayError(e);
|
||||
} catch (GeneralSecurityException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
displayError(e);
|
||||
} catch (KeytoolException e) {
|
||||
displayError(e);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -152,8 +278,13 @@ public class ExportWizard extends Wizard implements IExportWizard {
|
||||
|
||||
@Override
|
||||
public boolean canFinish() {
|
||||
// check if we have the apk to resign, the destination location, and either
|
||||
// a private key/certificate or the creation mode. In creation mode, unless
|
||||
// all the key/keystore info is valid, the user cannot reach the last page, so there's
|
||||
// no need to check them again here.
|
||||
return mApkFilePath != null &&
|
||||
mPrivateKey != null && mCertificate != null &&
|
||||
((mPrivateKey != null && mCertificate != null)
|
||||
|| mKeystoreCreationMode || mKeyCreationMode) &&
|
||||
mDestinationPath != null;
|
||||
}
|
||||
|
||||
@@ -175,6 +306,22 @@ public class ExportWizard extends Wizard implements IExportWizard {
|
||||
}
|
||||
}
|
||||
|
||||
ExportWizardPage getKeystoreSelectionPage() {
|
||||
return mKeystoreSelectionPage;
|
||||
}
|
||||
|
||||
ExportWizardPage getKeyCreationPage() {
|
||||
return mKeyCreationPage;
|
||||
}
|
||||
|
||||
ExportWizardPage getKeySelectionPage() {
|
||||
return mKeySelectionPage;
|
||||
}
|
||||
|
||||
ExportWizardPage getKeyCheckPage() {
|
||||
return mKeyCheckPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an image descriptor for the wizard logo.
|
||||
*/
|
||||
@@ -192,10 +339,7 @@ public class ExportWizard extends Wizard implements IExportWizard {
|
||||
mApkFilePath = apkFilePath;
|
||||
mApkFileName = filename;
|
||||
|
||||
// indicate to the page that the project was changed.
|
||||
for (ExportWizardPage page : mPages) {
|
||||
page.newProjectReference();
|
||||
}
|
||||
updatePageOnChange(ExportWizardPage.DATA_PROJECT);
|
||||
}
|
||||
|
||||
String getApkFilename() {
|
||||
@@ -206,42 +350,95 @@ public class ExportWizard extends Wizard implements IExportWizard {
|
||||
mKeystore = path;
|
||||
mPrivateKey = null;
|
||||
mCertificate = null;
|
||||
|
||||
updatePageOnChange(ExportWizardPage.DATA_KEYSTORE);
|
||||
}
|
||||
|
||||
String getKeystore() {
|
||||
return mKeystore;
|
||||
}
|
||||
|
||||
void setKeystoreCreationMode(boolean createStore) {
|
||||
mKeystoreCreationMode = createStore;
|
||||
updatePageOnChange(ExportWizardPage.DATA_KEYSTORE);
|
||||
}
|
||||
|
||||
boolean getKeystoreCreationMode() {
|
||||
return mKeystoreCreationMode;
|
||||
}
|
||||
|
||||
|
||||
void setKeystorePassword(String password) {
|
||||
mKeystorePassword = password;
|
||||
mPrivateKey = null;
|
||||
mCertificate = null;
|
||||
|
||||
updatePageOnChange(ExportWizardPage.DATA_KEYSTORE);
|
||||
}
|
||||
|
||||
String getKeystorePassword() {
|
||||
return mKeystorePassword;
|
||||
}
|
||||
|
||||
void setKeyCreationMode(boolean createKey) {
|
||||
mKeyCreationMode = createKey;
|
||||
updatePageOnChange(ExportWizardPage.DATA_KEY);
|
||||
}
|
||||
|
||||
boolean getKeyCreationMode() {
|
||||
return mKeyCreationMode;
|
||||
}
|
||||
|
||||
void setExistingAliases(List<String> aliases) {
|
||||
mExistingAliases = aliases;
|
||||
}
|
||||
|
||||
List<String> getExistingAliases() {
|
||||
return mExistingAliases;
|
||||
}
|
||||
|
||||
void setKeyAlias(String name) {
|
||||
mKeyAlias = name;
|
||||
mPrivateKey = null;
|
||||
mCertificate = null;
|
||||
|
||||
updatePageOnChange(ExportWizardPage.DATA_KEY);
|
||||
}
|
||||
|
||||
String getKeyAlias() {
|
||||
return mKeyAlias;
|
||||
}
|
||||
|
||||
void setKeystorePassword(char[] password) {
|
||||
mKeystorePassword = password;
|
||||
mPrivateKey = null;
|
||||
mCertificate = null;
|
||||
}
|
||||
|
||||
char[] getKeystorePassword() {
|
||||
return mKeystorePassword;
|
||||
}
|
||||
|
||||
void setKeyPassword(char[] password) {
|
||||
|
||||
void setKeyPassword(String password) {
|
||||
mKeyPassword = password;
|
||||
mPrivateKey = null;
|
||||
mCertificate = null;
|
||||
|
||||
updatePageOnChange(ExportWizardPage.DATA_KEY);
|
||||
}
|
||||
|
||||
char[] getKeyPassword() {
|
||||
String getKeyPassword() {
|
||||
return mKeyPassword;
|
||||
}
|
||||
|
||||
void setValidity(int validity) {
|
||||
mValidity = validity;
|
||||
updatePageOnChange(ExportWizardPage.DATA_KEY);
|
||||
}
|
||||
|
||||
int getValidity() {
|
||||
return mValidity;
|
||||
}
|
||||
|
||||
void setDName(String dName) {
|
||||
mDName = dName;
|
||||
updatePageOnChange(ExportWizardPage.DATA_KEY);
|
||||
}
|
||||
|
||||
String getDName() {
|
||||
return mDName;
|
||||
}
|
||||
|
||||
void setSigningInfo(PrivateKey privateKey, X509Certificate certificate) {
|
||||
mPrivateKey = privateKey;
|
||||
mCertificate = certificate;
|
||||
@@ -251,4 +448,54 @@ public class ExportWizard extends Wizard implements IExportWizard {
|
||||
mDestinationPath = path;
|
||||
}
|
||||
|
||||
void updatePageOnChange(int changeMask) {
|
||||
for (ExportWizardPage page : mPages) {
|
||||
page.projectDataChanged(changeMask);
|
||||
}
|
||||
}
|
||||
|
||||
private void displayError(String... messages) {
|
||||
String message = null;
|
||||
if (messages.length == 1) {
|
||||
message = messages[0];
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder(messages[0]);
|
||||
for (int i = 1; i < messages.length; i++) {
|
||||
sb.append('\n');
|
||||
sb.append(messages[i]);
|
||||
}
|
||||
|
||||
message = sb.toString();
|
||||
}
|
||||
|
||||
AdtPlugin.displayError("Export Wizard", message);
|
||||
}
|
||||
|
||||
private void displayError(Exception e) {
|
||||
String message = getExceptionMessage(e);
|
||||
displayError(message);
|
||||
|
||||
AdtPlugin.log(e, "Export Wizard Error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Throwable#getMessage()}. If the {@link Throwable#getMessage()} returns
|
||||
* <code>null</code>, the method is called again on the cause of the Throwable object.
|
||||
* <p/>If no Throwable in the chain has a valid message, the canonical name of the first
|
||||
* exception is returned.
|
||||
*/
|
||||
private static String getExceptionMessage(Throwable t) {
|
||||
String message = t.getMessage();
|
||||
if (message == null) {
|
||||
Throwable cause = t.getCause();
|
||||
if (cause != null) {
|
||||
return getExceptionMessage(cause);
|
||||
}
|
||||
|
||||
// no more cause and still no message. display the first exception.
|
||||
return cause.getClass().getCanonicalName();
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,275 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.project.export;
|
||||
|
||||
import com.android.ide.eclipse.adt.project.ProjectHelper;
|
||||
import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.ModifyEvent;
|
||||
import org.eclipse.swt.events.ModifyListener;
|
||||
import org.eclipse.swt.events.SelectionAdapter;
|
||||
import org.eclipse.swt.events.SelectionEvent;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Button;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.FileDialog;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
import org.eclipse.ui.forms.widgets.FormText;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.UnrecoverableEntryException;
|
||||
import java.security.KeyStore.PrivateKeyEntry;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Calendar;
|
||||
|
||||
public class FinalExportPage extends ExportWizardPage {
|
||||
|
||||
private final ExportWizard mWizard;
|
||||
private PrivateKey mPrivateKey;
|
||||
private X509Certificate mCertificate;
|
||||
private Text mDestination;
|
||||
private Button mBrowseButton;
|
||||
private boolean mFatalSigningError;
|
||||
private FormText mDetailText;
|
||||
|
||||
protected FinalExportPage(ExportWizard wizard, String pageName) {
|
||||
super(pageName);
|
||||
mWizard = wizard;
|
||||
|
||||
setTitle("Application Export");
|
||||
setDescription("Export the signed Application package.");
|
||||
}
|
||||
|
||||
public void createControl(Composite parent) {
|
||||
setErrorMessage(null);
|
||||
setMessage(null);
|
||||
|
||||
// build the ui.
|
||||
Composite composite = new Composite(parent, SWT.NULL);
|
||||
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
|
||||
GridLayout gl = new GridLayout(3, false);
|
||||
gl.verticalSpacing *= 3;
|
||||
composite.setLayout(gl);
|
||||
|
||||
GridData gd;
|
||||
|
||||
new Label(composite, SWT.NONE).setText("Destination APK file:");
|
||||
mDestination = new Text(composite, SWT.BORDER);
|
||||
mDestination.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
mDestination.addModifyListener(new ModifyListener() {
|
||||
public void modifyText(ModifyEvent e) {
|
||||
onDestinationChange();
|
||||
}
|
||||
});
|
||||
mBrowseButton = new Button(composite, SWT.PUSH);
|
||||
mBrowseButton.setText("Browse...");
|
||||
mBrowseButton.addSelectionListener(new SelectionAdapter() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent e) {
|
||||
FileDialog fileDialog = new FileDialog(mBrowseButton.getShell(), SWT.SAVE);
|
||||
|
||||
fileDialog.setText("Destination file name");
|
||||
fileDialog.setFileName(mWizard.getApkFilename());
|
||||
|
||||
String saveLocation = fileDialog.open();
|
||||
if (saveLocation != null) {
|
||||
mDestination.setText(saveLocation);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mDetailText = new FormText(composite, SWT.NONE);
|
||||
mDetailText.setLayoutData(gd = new GridData(GridData.FILL_BOTH));
|
||||
gd.horizontalSpan = 3;
|
||||
|
||||
setControl(composite);
|
||||
}
|
||||
|
||||
@Override
|
||||
void onShow() {
|
||||
// fill the texts with information loaded from the project.
|
||||
if (mNewProjectReference) {
|
||||
// reset the destination from the content of the project
|
||||
IProject project = mWizard.getProject();
|
||||
|
||||
String destination = ProjectHelper.loadStringProperty(project,
|
||||
ExportWizard.PROPERTY_DESTINATION);
|
||||
if (destination != null) {
|
||||
mDestination.setText(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// reset the wizard with no key/cert to make it not finishable, unless a valid
|
||||
// key/cert is found.
|
||||
mWizard.setSigningInfo(null, null);
|
||||
|
||||
try {
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
FileInputStream fis = new FileInputStream(mWizard.getKeystore());
|
||||
keyStore.load(fis, mWizard.getKeystorePassword());
|
||||
fis.close();
|
||||
PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(
|
||||
mWizard.getKeyAlias(),
|
||||
new KeyStore.PasswordProtection(mWizard.getKeyPassword()));
|
||||
|
||||
if (entry != null) {
|
||||
mPrivateKey = entry.getPrivateKey();
|
||||
mCertificate = (X509Certificate)entry.getCertificate();
|
||||
} else {
|
||||
setErrorMessage("Unable to find key");
|
||||
|
||||
setPageComplete(false);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// this was checked at the first previous step and will not happen here, unless
|
||||
// the file was removed during the export wizard execution.
|
||||
onException(e);
|
||||
} catch (KeyStoreException e) {
|
||||
onException(e);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
onException(e);
|
||||
} catch (UnrecoverableEntryException e) {
|
||||
onException(e);
|
||||
} catch (CertificateException e) {
|
||||
onException(e);
|
||||
} catch (IOException e) {
|
||||
onException(e);
|
||||
}
|
||||
|
||||
if (mPrivateKey != null && mCertificate != null) {
|
||||
mFatalSigningError = false;
|
||||
|
||||
Calendar expirationCalendar = Calendar.getInstance();
|
||||
expirationCalendar.setTime(mCertificate.getNotAfter());
|
||||
Calendar today = Calendar.getInstance();
|
||||
|
||||
if (expirationCalendar.before(today)) {
|
||||
mDetailText.setText(String.format("<form><p>Certificate expired on %s</p></form>",
|
||||
mCertificate.getNotAfter().toString()),
|
||||
true /* parseTags */, true /* expandURLs */);
|
||||
|
||||
// fatal error = nothing can make the page complete.
|
||||
mFatalSigningError = true;
|
||||
|
||||
setErrorMessage("Certificate is expired!");
|
||||
setPageComplete(false);
|
||||
} else {
|
||||
// valid, key/cert: put it in the wizard so that it can be finished
|
||||
mWizard.setSigningInfo(mPrivateKey, mCertificate);
|
||||
|
||||
StringBuilder sb = new StringBuilder(String.format("<form><p>Certificate expires on %s.</p>",
|
||||
mCertificate.getNotAfter().toString()));
|
||||
|
||||
int expirationYear = expirationCalendar.get(Calendar.YEAR);
|
||||
int thisYear = today.get(Calendar.YEAR);
|
||||
|
||||
if (thisYear + 25 < expirationYear) {
|
||||
// do nothing
|
||||
} else {
|
||||
if (expirationYear == thisYear) {
|
||||
sb.append("<p>The certificate expires this year!</p>");
|
||||
} else {
|
||||
int count = expirationYear-thisYear;
|
||||
sb.append(String.format("<p>The Certificate expires in %1$s %2$s!</p>",
|
||||
count, count == 1 ? "year" : "years"));
|
||||
}
|
||||
|
||||
sb.append("<p>Make sure the certificate is valid for the planned lifetime of the product.</p>");
|
||||
sb.append("<p>If the certificate expires, you will be forced to sign your application with a different one.</p>");
|
||||
sb.append("<p>Applications cannot be upgraded if their certificate changes from one version to another, ");
|
||||
sb.append("forcing a full uninstall/install, which will make the user lose his/her data.</p>");
|
||||
sb.append("<p>Android Market currently requires certificates to be valid until 2033.</p>");
|
||||
|
||||
sb.append("</form>");
|
||||
}
|
||||
|
||||
mDetailText.setText(sb.toString(), true /* parseTags */, true /* expandURLs */);
|
||||
}
|
||||
mDetailText.getParent().layout();
|
||||
} else {
|
||||
// fatal error = nothing can make the page complete.
|
||||
mFatalSigningError = true;
|
||||
}
|
||||
|
||||
onDestinationChange();
|
||||
}
|
||||
|
||||
private void onDestinationChange() {
|
||||
if (mFatalSigningError == false) {
|
||||
String path = mDestination.getText().trim();
|
||||
if (path.length() == 0) {
|
||||
setErrorMessage("Enter destination for the APK file.");
|
||||
mWizard.setDestination(null); // this is to reset canFinish in the wizard
|
||||
setPageComplete(false);
|
||||
} else {
|
||||
File file = new File(path);
|
||||
if (file.isDirectory()) {
|
||||
setErrorMessage("Destination is a directory!");
|
||||
mWizard.setDestination(null); // this is to reset canFinish in the wizard
|
||||
setPageComplete(false);
|
||||
} else {
|
||||
File parentFile = file.getParentFile();
|
||||
if (parentFile == null || parentFile.isDirectory() == false) {
|
||||
setErrorMessage("Not a valid directory.");
|
||||
mWizard.setDestination(null); // this is to reset canFinish in the wizard
|
||||
setPageComplete(false);
|
||||
} else {
|
||||
mWizard.setDestination(path);
|
||||
setErrorMessage(null);
|
||||
setPageComplete(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #setErrorMessage(String)} and {@link #setPageComplete(boolean)} based on a
|
||||
* {@link Throwable} object. If the {@link Throwable#getMessage()} returns <code>null</code>,
|
||||
* the method is called again on the cause of the Throwable.
|
||||
*/
|
||||
private void onException(Throwable t) {
|
||||
String message = t.getMessage();
|
||||
if (message == null) {
|
||||
Throwable cause = t.getCause();
|
||||
if (cause != null) {
|
||||
onException(cause);
|
||||
} else {
|
||||
// no more cause and still no message. display the first exception.
|
||||
setErrorMessage(cause.getClass().getCanonicalName());
|
||||
setPageComplete(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setErrorMessage(message);
|
||||
setPageComplete(false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.project.export;
|
||||
|
||||
import com.android.ide.eclipse.adt.project.ProjectHelper;
|
||||
import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.ModifyEvent;
|
||||
import org.eclipse.swt.events.ModifyListener;
|
||||
import org.eclipse.swt.events.SelectionAdapter;
|
||||
import org.eclipse.swt.events.SelectionEvent;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Button;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.FileDialog;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
import org.eclipse.ui.forms.widgets.FormText;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.UnrecoverableEntryException;
|
||||
import java.security.KeyStore.PrivateKeyEntry;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Calendar;
|
||||
|
||||
/**
|
||||
* Final page of the wizard that checks the key and ask for the ouput location.
|
||||
*/
|
||||
final class KeyCheckPage extends ExportWizardPage {
|
||||
|
||||
private final ExportWizard mWizard;
|
||||
private PrivateKey mPrivateKey;
|
||||
private X509Certificate mCertificate;
|
||||
private Text mDestination;
|
||||
private boolean mFatalSigningError;
|
||||
private FormText mDetailText;
|
||||
|
||||
protected KeyCheckPage(ExportWizard wizard, String pageName) {
|
||||
super(pageName);
|
||||
mWizard = wizard;
|
||||
|
||||
setTitle("Destination and key/certificate checks");
|
||||
setDescription(""); // TODO
|
||||
}
|
||||
|
||||
public void createControl(Composite parent) {
|
||||
setErrorMessage(null);
|
||||
setMessage(null);
|
||||
|
||||
// build the ui.
|
||||
Composite composite = new Composite(parent, SWT.NULL);
|
||||
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
|
||||
GridLayout gl = new GridLayout(3, false);
|
||||
gl.verticalSpacing *= 3;
|
||||
composite.setLayout(gl);
|
||||
|
||||
GridData gd;
|
||||
|
||||
new Label(composite, SWT.NONE).setText("Destination APK file:");
|
||||
mDestination = new Text(composite, SWT.BORDER);
|
||||
mDestination.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
mDestination.addModifyListener(new ModifyListener() {
|
||||
public void modifyText(ModifyEvent e) {
|
||||
onDestinationChange();
|
||||
}
|
||||
});
|
||||
final Button browseButton = new Button(composite, SWT.PUSH);
|
||||
browseButton.setText("Browse...");
|
||||
browseButton.addSelectionListener(new SelectionAdapter() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent e) {
|
||||
FileDialog fileDialog = new FileDialog(browseButton.getShell(), SWT.SAVE);
|
||||
|
||||
fileDialog.setText("Destination file name");
|
||||
fileDialog.setFileName(mWizard.getApkFilename());
|
||||
|
||||
String saveLocation = fileDialog.open();
|
||||
if (saveLocation != null) {
|
||||
mDestination.setText(saveLocation);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mDetailText = new FormText(composite, SWT.NONE);
|
||||
mDetailText.setLayoutData(gd = new GridData(GridData.FILL_BOTH));
|
||||
gd.horizontalSpan = 3;
|
||||
|
||||
setControl(composite);
|
||||
}
|
||||
|
||||
@Override
|
||||
void onShow() {
|
||||
// fill the texts with information loaded from the project.
|
||||
if ((mProjectDataChanged & DATA_PROJECT) != 0) {
|
||||
// reset the destination from the content of the project
|
||||
IProject project = mWizard.getProject();
|
||||
|
||||
String destination = ProjectHelper.loadStringProperty(project,
|
||||
ExportWizard.PROPERTY_DESTINATION);
|
||||
if (destination != null) {
|
||||
mDestination.setText(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// if anything change we basically reload the data.
|
||||
if (mProjectDataChanged != 0) {
|
||||
mFatalSigningError = false;
|
||||
|
||||
// reset the wizard with no key/cert to make it not finishable, unless a valid
|
||||
// key/cert is found.
|
||||
mWizard.setSigningInfo(null, null);
|
||||
|
||||
if (mWizard.getKeystoreCreationMode() || mWizard.getKeyCreationMode()) {
|
||||
int validity = mWizard.getValidity();
|
||||
StringBuilder sb = new StringBuilder(
|
||||
String.format("<form><p>Certificate expires in %d years.</p>",
|
||||
validity));
|
||||
|
||||
if (validity < 25) {
|
||||
sb.append("<p>Make sure the certificate is valid for the planned lifetime of the product.</p>");
|
||||
sb.append("<p>If the certificate expires, you will be forced to sign your application with a different one.</p>");
|
||||
sb.append("<p>Applications cannot be upgraded if their certificate changes from one version to another, ");
|
||||
sb.append("forcing a full uninstall/install, which will make the user lose his/her data.</p>");
|
||||
sb.append("<p>Android Market currently requires certificates to be valid until 2033.</p>");
|
||||
}
|
||||
|
||||
sb.append("</form>");
|
||||
mDetailText.setText(sb.toString(), true /* parseTags */, true /* expandURLs */);
|
||||
} else {
|
||||
try {
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
FileInputStream fis = new FileInputStream(mWizard.getKeystore());
|
||||
keyStore.load(fis, mWizard.getKeystorePassword().toCharArray());
|
||||
fis.close();
|
||||
PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(
|
||||
mWizard.getKeyAlias(),
|
||||
new KeyStore.PasswordProtection(
|
||||
mWizard.getKeyPassword().toCharArray()));
|
||||
|
||||
if (entry != null) {
|
||||
mPrivateKey = entry.getPrivateKey();
|
||||
mCertificate = (X509Certificate)entry.getCertificate();
|
||||
} else {
|
||||
setErrorMessage("Unable to find key.");
|
||||
|
||||
setPageComplete(false);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// this was checked at the first previous step and will not happen here, unless
|
||||
// the file was removed during the export wizard execution.
|
||||
onException(e);
|
||||
} catch (KeyStoreException e) {
|
||||
onException(e);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
onException(e);
|
||||
} catch (UnrecoverableEntryException e) {
|
||||
onException(e);
|
||||
} catch (CertificateException e) {
|
||||
onException(e);
|
||||
} catch (IOException e) {
|
||||
onException(e);
|
||||
}
|
||||
|
||||
if (mPrivateKey != null && mCertificate != null) {
|
||||
Calendar expirationCalendar = Calendar.getInstance();
|
||||
expirationCalendar.setTime(mCertificate.getNotAfter());
|
||||
Calendar today = Calendar.getInstance();
|
||||
|
||||
if (expirationCalendar.before(today)) {
|
||||
mDetailText.setText(String.format(
|
||||
"<form><p>Certificate expired on %s</p></form>",
|
||||
mCertificate.getNotAfter().toString()),
|
||||
true /* parseTags */, true /* expandURLs */);
|
||||
|
||||
// fatal error = nothing can make the page complete.
|
||||
mFatalSigningError = true;
|
||||
|
||||
setErrorMessage("Certificate is expired.");
|
||||
setPageComplete(false);
|
||||
} else {
|
||||
// valid, key/cert: put it in the wizard so that it can be finished
|
||||
mWizard.setSigningInfo(mPrivateKey, mCertificate);
|
||||
|
||||
StringBuilder sb = new StringBuilder(String.format(
|
||||
"<form><p>Certificate expires on %s.</p>",
|
||||
mCertificate.getNotAfter().toString()));
|
||||
|
||||
int expirationYear = expirationCalendar.get(Calendar.YEAR);
|
||||
int thisYear = today.get(Calendar.YEAR);
|
||||
|
||||
if (thisYear + 25 < expirationYear) {
|
||||
// do nothing
|
||||
} else {
|
||||
if (expirationYear == thisYear) {
|
||||
sb.append("<p>The certificate expires this year.</p>");
|
||||
} else {
|
||||
int count = expirationYear-thisYear;
|
||||
sb.append(String.format(
|
||||
"<p>The Certificate expires in %1$s %2$s.</p>",
|
||||
count, count == 1 ? "year" : "years"));
|
||||
}
|
||||
|
||||
sb.append("<p>Make sure the certificate is valid for the planned lifetime of the product.</p>");
|
||||
sb.append("<p>If the certificate expires, you will be forced to sign your application with a different one.</p>");
|
||||
sb.append("<p>Applications cannot be upgraded if their certificate changes from one version to another, ");
|
||||
sb.append("forcing a full uninstall/install, which will make the user lose his/her data.</p>");
|
||||
sb.append("<p>Android Market currently requires certificates to be valid until 2033.</p>");
|
||||
}
|
||||
|
||||
sb.append("</form>");
|
||||
|
||||
mDetailText.setText(sb.toString(), true /* parseTags */, true /* expandURLs */);
|
||||
}
|
||||
mDetailText.getParent().layout();
|
||||
} else {
|
||||
// fatal error = nothing can make the page complete.
|
||||
mFatalSigningError = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onDestinationChange();
|
||||
}
|
||||
|
||||
private void onDestinationChange() {
|
||||
if (mFatalSigningError == false) {
|
||||
// reset messages for now.
|
||||
setErrorMessage(null);
|
||||
setMessage(null);
|
||||
|
||||
String path = mDestination.getText().trim();
|
||||
|
||||
if (path.length() == 0) {
|
||||
setErrorMessage("Enter destination for the APK file.");
|
||||
mWizard.setDestination(null); // this is to reset canFinish in the wizard
|
||||
setPageComplete(false);
|
||||
return;
|
||||
}
|
||||
|
||||
File file = new File(path);
|
||||
if (file.isDirectory()) {
|
||||
setErrorMessage("Destination is a directory.");
|
||||
mWizard.setDestination(null); // this is to reset canFinish in the wizard
|
||||
setPageComplete(false);
|
||||
return;
|
||||
}
|
||||
|
||||
File parentFile = file.getParentFile();
|
||||
if (parentFile == null || parentFile.isDirectory() == false) {
|
||||
setErrorMessage("Not a valid directory.");
|
||||
mWizard.setDestination(null); // this is to reset canFinish in the wizard
|
||||
setPageComplete(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// no error, set the destination in the wizard.
|
||||
mWizard.setDestination(path);
|
||||
setPageComplete(true);
|
||||
|
||||
// However, we should also test if the file already exists.
|
||||
if (file.isFile()) {
|
||||
setMessage("Destination file already exists.", WARNING);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.project.export;
|
||||
|
||||
import com.android.ide.eclipse.adt.project.ProjectHelper;
|
||||
import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.jface.wizard.IWizardPage;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.ModifyEvent;
|
||||
import org.eclipse.swt.events.ModifyListener;
|
||||
import org.eclipse.swt.events.VerifyEvent;
|
||||
import org.eclipse.swt.events.VerifyListener;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Key creation page.
|
||||
*/
|
||||
final class KeyCreationPage extends ExportWizardPage {
|
||||
|
||||
private final ExportWizard mWizard;
|
||||
private Text mAlias;
|
||||
private Text mKeyPassword;
|
||||
private Text mKeyPassword2;
|
||||
private Text mCnField;
|
||||
private boolean mDisableOnChange = false;
|
||||
private Text mOuField;
|
||||
private Text mOField;
|
||||
private Text mLField;
|
||||
private Text mStField;
|
||||
private Text mCField;
|
||||
private String mDName;
|
||||
private int mValidity = 0;
|
||||
private List<String> mExistingAliases;
|
||||
|
||||
|
||||
protected KeyCreationPage(ExportWizard wizard, String pageName) {
|
||||
super(pageName);
|
||||
mWizard = wizard;
|
||||
|
||||
setTitle("Key Creation");
|
||||
setDescription(""); // TODO?
|
||||
}
|
||||
|
||||
public void createControl(Composite parent) {
|
||||
Composite composite = new Composite(parent, SWT.NULL);
|
||||
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
|
||||
GridLayout gl = new GridLayout(2, false);
|
||||
composite.setLayout(gl);
|
||||
|
||||
GridData gd;
|
||||
|
||||
new Label(composite, SWT.NONE).setText("Alias:");
|
||||
mAlias = new Text(composite, SWT.BORDER);
|
||||
mAlias.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
|
||||
new Label(composite, SWT.NONE).setText("Password:");
|
||||
mKeyPassword = new Text(composite, SWT.BORDER | SWT.PASSWORD);
|
||||
mKeyPassword.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
mKeyPassword.addVerifyListener(sPasswordVerifier);
|
||||
|
||||
new Label(composite, SWT.NONE).setText("Confirm:");
|
||||
mKeyPassword2 = new Text(composite, SWT.BORDER | SWT.PASSWORD);
|
||||
mKeyPassword2.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
mKeyPassword2.addVerifyListener(sPasswordVerifier);
|
||||
|
||||
new Label(composite, SWT.NONE).setText("Validity (years):");
|
||||
final Text validityText = new Text(composite, SWT.BORDER);
|
||||
validityText.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
validityText.addVerifyListener(new VerifyListener() {
|
||||
public void verifyText(VerifyEvent e) {
|
||||
// check for digit only.
|
||||
for (int i = 0 ; i < e.text.length(); i++) {
|
||||
char letter = e.text.charAt(i);
|
||||
if (letter < '0' || letter > '9') {
|
||||
e.doit = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL).setLayoutData(
|
||||
gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
gd.horizontalSpan = 2;
|
||||
|
||||
new Label(composite, SWT.NONE).setText("First and Last Name:");
|
||||
mCnField = new Text(composite, SWT.BORDER);
|
||||
mCnField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
|
||||
new Label(composite, SWT.NONE).setText("Organizational Unit:");
|
||||
mOuField = new Text(composite, SWT.BORDER);
|
||||
mOuField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
|
||||
new Label(composite, SWT.NONE).setText("Organization:");
|
||||
mOField = new Text(composite, SWT.BORDER);
|
||||
mOField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
|
||||
new Label(composite, SWT.NONE).setText("City or Locality:");
|
||||
mLField = new Text(composite, SWT.BORDER);
|
||||
mLField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
|
||||
new Label(composite, SWT.NONE).setText("State or Province:");
|
||||
mStField = new Text(composite, SWT.BORDER);
|
||||
mStField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
|
||||
new Label(composite, SWT.NONE).setText("Country Code (XX):");
|
||||
mCField = new Text(composite, SWT.BORDER);
|
||||
mCField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
|
||||
// Show description the first time
|
||||
setErrorMessage(null);
|
||||
setMessage(null);
|
||||
setControl(composite);
|
||||
|
||||
mAlias.addModifyListener(new ModifyListener() {
|
||||
public void modifyText(ModifyEvent e) {
|
||||
mWizard.setKeyAlias(mAlias.getText().trim());
|
||||
onChange();
|
||||
}
|
||||
});
|
||||
mKeyPassword.addModifyListener(new ModifyListener() {
|
||||
public void modifyText(ModifyEvent e) {
|
||||
mWizard.setKeyPassword(mKeyPassword.getText());
|
||||
onChange();
|
||||
}
|
||||
});
|
||||
mKeyPassword2.addModifyListener(new ModifyListener() {
|
||||
public void modifyText(ModifyEvent e) {
|
||||
onChange();
|
||||
}
|
||||
});
|
||||
|
||||
validityText.addModifyListener(new ModifyListener() {
|
||||
public void modifyText(ModifyEvent e) {
|
||||
try {
|
||||
mValidity = Integer.parseInt(validityText.getText());
|
||||
} catch (NumberFormatException e2) {
|
||||
// this should only happen if the text field is empty due to the verifyListener.
|
||||
mValidity = 0;
|
||||
}
|
||||
mWizard.setValidity(mValidity);
|
||||
onChange();
|
||||
}
|
||||
});
|
||||
|
||||
ModifyListener dNameListener = new ModifyListener() {
|
||||
public void modifyText(ModifyEvent e) {
|
||||
onDNameChange();
|
||||
}
|
||||
};
|
||||
|
||||
mCnField.addModifyListener(dNameListener);
|
||||
mOuField.addModifyListener(dNameListener);
|
||||
mOField.addModifyListener(dNameListener);
|
||||
mLField.addModifyListener(dNameListener);
|
||||
mStField.addModifyListener(dNameListener);
|
||||
mCField.addModifyListener(dNameListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
void onShow() {
|
||||
// fill the texts with information loaded from the project.
|
||||
if ((mProjectDataChanged & (DATA_PROJECT | DATA_KEYSTORE)) != 0) {
|
||||
// reset the keystore/alias from the content of the project
|
||||
IProject project = mWizard.getProject();
|
||||
|
||||
// disable onChange for now. we'll call it once at the end.
|
||||
mDisableOnChange = true;
|
||||
|
||||
String alias = ProjectHelper.loadStringProperty(project, ExportWizard.PROPERTY_ALIAS);
|
||||
if (alias != null) {
|
||||
mAlias.setText(alias);
|
||||
}
|
||||
|
||||
// get the existing list of keys if applicable
|
||||
if (mWizard.getKeyCreationMode()) {
|
||||
mExistingAliases = mWizard.getExistingAliases();
|
||||
} else {
|
||||
mExistingAliases = null;
|
||||
}
|
||||
|
||||
// reset the passwords
|
||||
mKeyPassword.setText(""); //$NON-NLS-1$
|
||||
mKeyPassword2.setText(""); //$NON-NLS-1$
|
||||
|
||||
// enable onChange, and call it to display errors and enable/disable pageCompleted.
|
||||
mDisableOnChange = false;
|
||||
onChange();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWizardPage getPreviousPage() {
|
||||
if (mWizard.getKeyCreationMode()) { // this means we create a key from an existing store
|
||||
return mWizard.getKeySelectionPage();
|
||||
}
|
||||
|
||||
return mWizard.getKeystoreSelectionPage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWizardPage getNextPage() {
|
||||
return mWizard.getKeyCheckPage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles changes and update the error message and calls {@link #setPageComplete(boolean)}.
|
||||
*/
|
||||
private void onChange() {
|
||||
if (mDisableOnChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
setErrorMessage(null);
|
||||
setMessage(null);
|
||||
|
||||
if (mAlias.getText().trim().length() == 0) {
|
||||
setErrorMessage("Enter key alias.");
|
||||
setPageComplete(false);
|
||||
return;
|
||||
} else if (mExistingAliases != null) {
|
||||
// we cannot use indexOf, because we need to do a case-insensitive check
|
||||
String keyAlias = mAlias.getText().trim();
|
||||
for (String alias : mExistingAliases) {
|
||||
if (alias.equalsIgnoreCase(keyAlias)) {
|
||||
setErrorMessage("Key alias already exists in keystore.");
|
||||
setPageComplete(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String value = mKeyPassword.getText();
|
||||
if (value.length() == 0) {
|
||||
setErrorMessage("Enter key password.");
|
||||
setPageComplete(false);
|
||||
return;
|
||||
} else if (value.length() < 6) {
|
||||
setErrorMessage("Key password is too short - must be at least 6 characters.");
|
||||
setPageComplete(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.equals(mKeyPassword2.getText()) == false) {
|
||||
setErrorMessage("Key passwords don't match.");
|
||||
setPageComplete(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mValidity == 0) {
|
||||
setErrorMessage("Key certificate validity is required.");
|
||||
setPageComplete(false);
|
||||
return;
|
||||
} else if (mValidity < 25) {
|
||||
setMessage("A 25 year certificate validity is recommended.", WARNING);
|
||||
} else if (mValidity > 1000) {
|
||||
setErrorMessage("Key certificate validity must be between 1 and 1000 years.");
|
||||
setPageComplete(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mDName == null || mDName.length() == 0) {
|
||||
setErrorMessage("At least one Certificate issuer field is required to be non-empty.");
|
||||
setPageComplete(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setPageComplete(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles changes in the DName fields.
|
||||
*/
|
||||
private void onDNameChange() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
buildDName("CN", mCnField, sb);
|
||||
buildDName("OU", mOuField, sb);
|
||||
buildDName("O", mOField, sb);
|
||||
buildDName("L", mLField, sb);
|
||||
buildDName("ST", mStField, sb);
|
||||
buildDName("C", mCField, sb);
|
||||
|
||||
mDName = sb.toString();
|
||||
mWizard.setDName(mDName);
|
||||
|
||||
onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the distinguished name string with the provided {@link StringBuilder}.
|
||||
* @param prefix the prefix of the entry.
|
||||
* @param textField The {@link Text} field containing the entry value.
|
||||
* @param sb the string builder containing the dname.
|
||||
*/
|
||||
private void buildDName(String prefix, Text textField, StringBuilder sb) {
|
||||
if (textField != null) {
|
||||
String value = textField.getText().trim();
|
||||
if (value.length() > 0) {
|
||||
if (sb.length() > 0) {
|
||||
sb.append(",");
|
||||
}
|
||||
|
||||
sb.append(prefix);
|
||||
sb.append('=');
|
||||
sb.append(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.project.export;
|
||||
|
||||
import com.android.ide.eclipse.adt.project.ProjectHelper;
|
||||
import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.jface.wizard.IWizardPage;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.ModifyEvent;
|
||||
import org.eclipse.swt.events.ModifyListener;
|
||||
import org.eclipse.swt.events.SelectionAdapter;
|
||||
import org.eclipse.swt.events.SelectionEvent;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Button;
|
||||
import org.eclipse.swt.widgets.Combo;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
|
||||
/**
|
||||
* Key Selection Page. This is used when an existing keystore is used.
|
||||
*/
|
||||
final class KeySelectionPage extends ExportWizardPage {
|
||||
|
||||
private final ExportWizard mWizard;
|
||||
private Label mKeyAliasesLabel;
|
||||
private Combo mKeyAliases;
|
||||
private Label mKeyPasswordLabel;
|
||||
private Text mKeyPassword;
|
||||
private boolean mDisableOnChange = false;
|
||||
private Button mUseExistingKey;
|
||||
private Button mCreateKey;
|
||||
|
||||
protected KeySelectionPage(ExportWizard wizard, String pageName) {
|
||||
super(pageName);
|
||||
mWizard = wizard;
|
||||
|
||||
setTitle("Key alias selection");
|
||||
setDescription(""); // TODO
|
||||
}
|
||||
|
||||
public void createControl(Composite parent) {
|
||||
Composite composite = new Composite(parent, SWT.NULL);
|
||||
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
|
||||
GridLayout gl = new GridLayout(3, false);
|
||||
composite.setLayout(gl);
|
||||
|
||||
GridData gd;
|
||||
|
||||
mUseExistingKey = new Button(composite, SWT.RADIO);
|
||||
mUseExistingKey.setText("Use existing key");
|
||||
mUseExistingKey.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
gd.horizontalSpan = 3;
|
||||
mUseExistingKey.setSelection(true);
|
||||
|
||||
new Composite(composite, SWT.NONE).setLayoutData(gd = new GridData());
|
||||
gd.heightHint = 0;
|
||||
gd.widthHint = 50;
|
||||
mKeyAliasesLabel = new Label(composite, SWT.NONE);
|
||||
mKeyAliasesLabel.setText("Alias:");
|
||||
mKeyAliases = new Combo(composite, SWT.READ_ONLY);
|
||||
mKeyAliases.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
|
||||
new Composite(composite, SWT.NONE).setLayoutData(gd = new GridData());
|
||||
gd.heightHint = 0;
|
||||
gd.widthHint = 50;
|
||||
mKeyPasswordLabel = new Label(composite, SWT.NONE);
|
||||
mKeyPasswordLabel.setText("Password:");
|
||||
mKeyPassword = new Text(composite, SWT.BORDER | SWT.PASSWORD);
|
||||
mKeyPassword.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
|
||||
mCreateKey = new Button(composite, SWT.RADIO);
|
||||
mCreateKey.setText("Create new key");
|
||||
mCreateKey.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
gd.horizontalSpan = 3;
|
||||
|
||||
// Show description the first time
|
||||
setErrorMessage(null);
|
||||
setMessage(null);
|
||||
setControl(composite);
|
||||
|
||||
mUseExistingKey.addSelectionListener(new SelectionAdapter() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent e) {
|
||||
mWizard.setKeyCreationMode(!mUseExistingKey.getSelection());
|
||||
enableWidgets();
|
||||
onChange();
|
||||
}
|
||||
});
|
||||
|
||||
mKeyAliases.addSelectionListener(new SelectionAdapter() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent e) {
|
||||
mWizard.setKeyAlias(mKeyAliases.getItem(mKeyAliases.getSelectionIndex()));
|
||||
onChange();
|
||||
}
|
||||
});
|
||||
|
||||
mKeyPassword.addModifyListener(new ModifyListener() {
|
||||
public void modifyText(ModifyEvent e) {
|
||||
mWizard.setKeyPassword(mKeyPassword.getText());
|
||||
onChange();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
void onShow() {
|
||||
// fill the texts with information loaded from the project.
|
||||
if ((mProjectDataChanged & (DATA_PROJECT | DATA_KEYSTORE)) != 0) {
|
||||
// disable onChange for now. we'll call it once at the end.
|
||||
mDisableOnChange = true;
|
||||
|
||||
// reset the alias from the content of the project
|
||||
try {
|
||||
// reset to using a key
|
||||
mWizard.setKeyCreationMode(false);
|
||||
mUseExistingKey.setSelection(true);
|
||||
mCreateKey.setSelection(false);
|
||||
enableWidgets();
|
||||
|
||||
// remove the content of the alias combo always and first, in case the
|
||||
// keystore password is wrong
|
||||
mKeyAliases.removeAll();
|
||||
|
||||
// get the alias list (also used as a keystore password test)
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
FileInputStream fis = new FileInputStream(mWizard.getKeystore());
|
||||
keyStore.load(fis, mWizard.getKeystorePassword().toCharArray());
|
||||
fis.close();
|
||||
|
||||
Enumeration<String> aliases = keyStore.aliases();
|
||||
|
||||
// get the alias from the project previous export, and look for a match as
|
||||
// we add the aliases to the combo.
|
||||
IProject project = mWizard.getProject();
|
||||
|
||||
String keyAlias = ProjectHelper.loadStringProperty(project,
|
||||
ExportWizard.PROPERTY_ALIAS);
|
||||
|
||||
ArrayList<String> aliasList = new ArrayList<String>();
|
||||
|
||||
int selection = -1;
|
||||
int count = 0;
|
||||
while (aliases.hasMoreElements()) {
|
||||
String alias = aliases.nextElement();
|
||||
mKeyAliases.add(alias);
|
||||
aliasList.add(alias);
|
||||
if (selection == -1 && alias.equalsIgnoreCase(keyAlias)) {
|
||||
selection = count;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
mWizard.setExistingAliases(aliasList);
|
||||
|
||||
if (selection != -1) {
|
||||
mKeyAliases.select(selection);
|
||||
|
||||
// since a match was found and is selected, we need to give it to
|
||||
// the wizard as well
|
||||
mWizard.setKeyAlias(keyAlias);
|
||||
} else {
|
||||
mKeyAliases.clearSelection();
|
||||
}
|
||||
|
||||
// reset the password
|
||||
mKeyPassword.setText(""); //$NON-NLS-1$
|
||||
|
||||
// enable onChange, and call it to display errors and enable/disable pageCompleted.
|
||||
mDisableOnChange = false;
|
||||
onChange();
|
||||
} catch (KeyStoreException e) {
|
||||
onException(e);
|
||||
} catch (FileNotFoundException e) {
|
||||
onException(e);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
onException(e);
|
||||
} catch (CertificateException e) {
|
||||
onException(e);
|
||||
} catch (IOException e) {
|
||||
onException(e);
|
||||
} finally {
|
||||
// in case we exit with an exception, we need to reset this
|
||||
mDisableOnChange = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWizardPage getPreviousPage() {
|
||||
return mWizard.getKeystoreSelectionPage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWizardPage getNextPage() {
|
||||
if (mWizard.getKeyCreationMode()) {
|
||||
return mWizard.getKeyCreationPage();
|
||||
}
|
||||
|
||||
return mWizard.getKeyCheckPage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles changes and update the error message and calls {@link #setPageComplete(boolean)}.
|
||||
*/
|
||||
private void onChange() {
|
||||
if (mDisableOnChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
setErrorMessage(null);
|
||||
setMessage(null);
|
||||
|
||||
if (mWizard.getKeyCreationMode() == false) {
|
||||
if (mKeyAliases.getSelectionIndex() == -1) {
|
||||
setErrorMessage("Select a key alias.");
|
||||
setPageComplete(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mKeyPassword.getText().trim().length() == 0) {
|
||||
setErrorMessage("Enter key password.");
|
||||
setPageComplete(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setPageComplete(true);
|
||||
}
|
||||
|
||||
private void enableWidgets() {
|
||||
boolean useKey = !mWizard.getKeyCreationMode();
|
||||
mKeyAliasesLabel.setEnabled(useKey);
|
||||
mKeyAliases.setEnabled(useKey);
|
||||
mKeyPassword.setEnabled(useKey);
|
||||
mKeyPasswordLabel.setEnabled(useKey);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import com.android.ide.eclipse.adt.project.ProjectHelper;
|
||||
import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.jface.wizard.IWizardPage;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.ModifyEvent;
|
||||
import org.eclipse.swt.events.ModifyListener;
|
||||
@@ -36,23 +37,26 @@ import org.eclipse.swt.widgets.Text;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Second export wizard page.
|
||||
* Keystore selection page. This page allows to choose to create a new keystore or use an
|
||||
* existing one.
|
||||
*/
|
||||
public class SigningExportPage extends ExportWizardPage {
|
||||
final class KeystoreSelectionPage extends ExportWizardPage {
|
||||
|
||||
private final ExportWizard mWizard;
|
||||
private Button mUseExistingKeystore;
|
||||
private Button mCreateKeystore;
|
||||
private Text mKeystore;
|
||||
private Text mAlias;
|
||||
private Text mKeystorePassword;
|
||||
private Text mKeyPassword;
|
||||
private Label mConfirmLabel;
|
||||
private Text mKeystorePassword2;
|
||||
private boolean mDisableOnChange = false;
|
||||
|
||||
protected SigningExportPage(ExportWizard wizard, String pageName) {
|
||||
protected KeystoreSelectionPage(ExportWizard wizard, String pageName) {
|
||||
super(pageName);
|
||||
mWizard = wizard;
|
||||
|
||||
setTitle("Application Signing");
|
||||
setDescription("Defines which store, key and certificate to use to sign the Android Application.");
|
||||
setTitle("Keystore selection");
|
||||
setDescription(""); //TODO
|
||||
}
|
||||
|
||||
public void createControl(Composite parent) {
|
||||
@@ -62,8 +66,19 @@ public class SigningExportPage extends ExportWizardPage {
|
||||
composite.setLayout(gl);
|
||||
|
||||
GridData gd;
|
||||
|
||||
mUseExistingKeystore = new Button(composite, SWT.RADIO);
|
||||
mUseExistingKeystore.setText("Use existing keystore");
|
||||
mUseExistingKeystore.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
gd.horizontalSpan = 3;
|
||||
mUseExistingKeystore.setSelection(true);
|
||||
|
||||
new Label(composite, SWT.NONE).setText("Keystore:");
|
||||
mCreateKeystore = new Button(composite, SWT.RADIO);
|
||||
mCreateKeystore.setText("Create new keystore");
|
||||
mCreateKeystore.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
gd.horizontalSpan = 3;
|
||||
|
||||
new Label(composite, SWT.NONE).setText("Location:");
|
||||
mKeystore = new Text(composite, SWT.BORDER);
|
||||
mKeystore.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
final Button browseButton = new Button(composite, SWT.PUSH);
|
||||
@@ -71,8 +86,14 @@ public class SigningExportPage extends ExportWizardPage {
|
||||
browseButton.addSelectionListener(new SelectionAdapter() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent e) {
|
||||
FileDialog fileDialog = new FileDialog(browseButton.getShell(), SWT.OPEN);
|
||||
fileDialog.setText("Load Keystore");
|
||||
FileDialog fileDialog;
|
||||
if (mUseExistingKeystore.getSelection()) {
|
||||
fileDialog = new FileDialog(browseButton.getShell(),SWT.OPEN);
|
||||
fileDialog.setText("Load Keystore");
|
||||
} else {
|
||||
fileDialog = new FileDialog(browseButton.getShell(),SWT.SAVE);
|
||||
fileDialog.setText("Select Keystore Name");
|
||||
}
|
||||
|
||||
String fileName = fileDialog.open();
|
||||
if (fileName != null) {
|
||||
@@ -80,64 +101,73 @@ public class SigningExportPage extends ExportWizardPage {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
new Composite(composite, SWT.NONE).setLayoutData(gd = new GridData());
|
||||
gd.horizontalSpan = 2;
|
||||
gd.heightHint = 0;
|
||||
new Button(composite, SWT.PUSH).setText("New...");
|
||||
|
||||
new Label(composite, SWT.NONE).setText("Key Alias:");
|
||||
mAlias = new Text(composite, SWT.BORDER);
|
||||
mAlias.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
|
||||
new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL).setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
gd.horizontalSpan = 3;
|
||||
|
||||
new Label(composite, SWT.NONE).setText("Store password:");
|
||||
new Label(composite, SWT.NONE).setText("Password:");
|
||||
mKeystorePassword = new Text(composite, SWT.BORDER | SWT.PASSWORD);
|
||||
mKeystorePassword.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
mKeystorePassword.addVerifyListener(sPasswordVerifier);
|
||||
new Composite(composite, SWT.NONE).setLayoutData(gd = new GridData());
|
||||
gd.heightHint = gd.widthHint = 0;
|
||||
|
||||
new Label(composite, SWT.NONE).setText("Key password:");
|
||||
mKeyPassword = new Text(composite, SWT.BORDER | SWT.PASSWORD);
|
||||
mKeyPassword.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
mConfirmLabel = new Label(composite, SWT.NONE);
|
||||
mConfirmLabel.setText("Confirm:");
|
||||
mKeystorePassword2 = new Text(composite, SWT.BORDER | SWT.PASSWORD);
|
||||
mKeystorePassword2.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
mKeystorePassword2.addVerifyListener(sPasswordVerifier);
|
||||
new Composite(composite, SWT.NONE).setLayoutData(gd = new GridData());
|
||||
gd.heightHint = gd.widthHint = 0;
|
||||
mKeystorePassword2.setEnabled(false);
|
||||
|
||||
// Show description the first time
|
||||
setErrorMessage(null);
|
||||
setMessage(null);
|
||||
setControl(composite);
|
||||
|
||||
mUseExistingKeystore.addSelectionListener(new SelectionAdapter() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent e) {
|
||||
boolean createStore = !mUseExistingKeystore.getSelection();
|
||||
mKeystorePassword2.setEnabled(createStore);
|
||||
mConfirmLabel.setEnabled(createStore);
|
||||
mWizard.setKeystoreCreationMode(createStore);
|
||||
onChange();
|
||||
}
|
||||
});
|
||||
|
||||
mKeystore.addModifyListener(new ModifyListener() {
|
||||
public void modifyText(ModifyEvent e) {
|
||||
mWizard.setKeystore(mKeystore.getText().trim());
|
||||
onChange();
|
||||
}
|
||||
});
|
||||
mAlias.addModifyListener(new ModifyListener() {
|
||||
public void modifyText(ModifyEvent e) {
|
||||
mWizard.setKeyAlias(mAlias.getText().trim());
|
||||
onChange();
|
||||
}
|
||||
});
|
||||
|
||||
mKeystorePassword.addModifyListener(new ModifyListener() {
|
||||
public void modifyText(ModifyEvent e) {
|
||||
mWizard.setKeystorePassword(mKeystorePassword.getText().trim().toCharArray());
|
||||
mWizard.setKeystorePassword(mKeystorePassword.getText());
|
||||
onChange();
|
||||
}
|
||||
});
|
||||
mKeyPassword.addModifyListener(new ModifyListener() {
|
||||
|
||||
mKeystorePassword2.addModifyListener(new ModifyListener() {
|
||||
public void modifyText(ModifyEvent e) {
|
||||
mWizard.setKeyPassword(mKeyPassword.getText().trim().toCharArray());
|
||||
onChange();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public IWizardPage getNextPage() {
|
||||
if (mUseExistingKeystore.getSelection()) {
|
||||
return mWizard.getKeySelectionPage();
|
||||
}
|
||||
|
||||
return mWizard.getKeyCreationPage();
|
||||
}
|
||||
|
||||
@Override
|
||||
void onShow() {
|
||||
// fill the texts with information loaded from the project.
|
||||
if (mNewProjectReference) {
|
||||
if ((mProjectDataChanged & DATA_PROJECT) != 0) {
|
||||
// reset the keystore/alias from the content of the project
|
||||
IProject project = mWizard.getProject();
|
||||
|
||||
@@ -150,14 +180,9 @@ public class SigningExportPage extends ExportWizardPage {
|
||||
mKeystore.setText(keystore);
|
||||
}
|
||||
|
||||
String alias = ProjectHelper.loadStringProperty(project, ExportWizard.PROPERTY_ALIAS);
|
||||
if (alias != null) {
|
||||
mAlias.setText(alias);
|
||||
}
|
||||
|
||||
// reset the passwords
|
||||
mKeystorePassword.setText(""); //$NON-NLS-1$
|
||||
mKeyPassword.setText(""); //$NON-NLS-1$
|
||||
mKeystorePassword2.setText(""); //$NON-NLS-1$
|
||||
|
||||
// enable onChange, and call it to display errors and enable/disable pageCompleted.
|
||||
mDisableOnChange = false;
|
||||
@@ -176,6 +201,8 @@ public class SigningExportPage extends ExportWizardPage {
|
||||
setErrorMessage(null);
|
||||
setMessage(null);
|
||||
|
||||
boolean createStore = !mUseExistingKeystore.getSelection();
|
||||
|
||||
// checks the keystore path is non null.
|
||||
String keystore = mKeystore.getText().trim();
|
||||
if (keystore.length() == 0) {
|
||||
@@ -185,34 +212,49 @@ public class SigningExportPage extends ExportWizardPage {
|
||||
} else {
|
||||
File f = new File(keystore);
|
||||
if (f.exists() == false) {
|
||||
setErrorMessage("Keystore does not exists!");
|
||||
setPageComplete(false);
|
||||
return;
|
||||
if (createStore == false) {
|
||||
setErrorMessage("Keystore does not exist.");
|
||||
setPageComplete(false);
|
||||
return;
|
||||
}
|
||||
} else if (f.isDirectory()) {
|
||||
setErrorMessage("Keystore is a directory!");
|
||||
setErrorMessage("Keystore path is a directory.");
|
||||
setPageComplete(false);
|
||||
return;
|
||||
} else if (f.isFile()) {
|
||||
if (createStore) {
|
||||
setErrorMessage("File already exists.");
|
||||
setPageComplete(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mAlias.getText().trim().length() == 0) {
|
||||
setErrorMessage("Enter key alias.");
|
||||
setPageComplete(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mKeystorePassword.getText().trim().length() == 0) {
|
||||
String value = mKeystorePassword.getText();
|
||||
if (value.length() == 0) {
|
||||
setErrorMessage("Enter keystore password.");
|
||||
setPageComplete(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mKeyPassword.getText().trim().length() == 0) {
|
||||
setErrorMessage("Enter key password.");
|
||||
} else if (createStore && value.length() < 6) {
|
||||
setErrorMessage("Keystore password is too short - must be at least 6 characters.");
|
||||
setPageComplete(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (createStore) {
|
||||
if (mKeystorePassword2.getText().length() == 0) {
|
||||
setErrorMessage("Confirm keystore password.");
|
||||
setPageComplete(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mKeystorePassword.getText().equals(mKeystorePassword2.getText()) == false) {
|
||||
setErrorMessage("Keystore passwords do not match.");
|
||||
setPageComplete(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setPageComplete(true);
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ import java.io.File;
|
||||
/**
|
||||
* First Export Wizard Page. Display warning/errors.
|
||||
*/
|
||||
public class PreExportPage extends ExportWizardPage {
|
||||
final class ProjectCheckPage extends ExportWizardPage {
|
||||
private final static String IMG_ERROR = "error.png"; //$NON-NLS-1$
|
||||
private final static String IMG_WARNING = "warning.png"; //$NON-NLS-1$
|
||||
|
||||
@@ -60,12 +60,13 @@ public class PreExportPage extends ExportWizardPage {
|
||||
private Composite mErrorComposite;
|
||||
private Text mProjectText;
|
||||
private ProjectChooserHelper mProjectChooserHelper;
|
||||
private boolean mFirstOnShow = true;
|
||||
|
||||
protected PreExportPage(ExportWizard wizard, String pageName) {
|
||||
protected ProjectCheckPage(ExportWizard wizard, String pageName) {
|
||||
super(pageName);
|
||||
mWizard = wizard;
|
||||
|
||||
setTitle("Pre Export Checks");
|
||||
setTitle("Project Checks");
|
||||
setDescription("Performs a set of checks to make sure the application can be exported.");
|
||||
}
|
||||
|
||||
@@ -123,10 +124,14 @@ public class PreExportPage extends ExportWizardPage {
|
||||
|
||||
@Override
|
||||
void onShow() {
|
||||
// get the project and init the ui
|
||||
IProject project = mWizard.getProject();
|
||||
if (project != null) {
|
||||
mProjectText.setText(project.getName());
|
||||
if (mFirstOnShow) {
|
||||
// get the project and init the ui
|
||||
IProject project = mWizard.getProject();
|
||||
if (project != null) {
|
||||
mProjectText.setText(project.getName());
|
||||
}
|
||||
|
||||
mFirstOnShow = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,16 +27,19 @@ class AndroidClasspathContainer implements IClasspathContainer {
|
||||
|
||||
private IClasspathEntry[] mClasspathEntry;
|
||||
private IPath mContainerPath;
|
||||
private String mName;
|
||||
|
||||
/**
|
||||
* Constructs the container with the {@link IClasspathEntry} representing the android
|
||||
* framework jar file and the container id
|
||||
* @param entry the entry representing the android framework.
|
||||
* @param path the path containing the classpath container id.
|
||||
* @param name the name of the container to display.
|
||||
*/
|
||||
AndroidClasspathContainer(IClasspathEntry entry, IPath path) {
|
||||
AndroidClasspathContainer(IClasspathEntry entry, IPath path, String name) {
|
||||
mClasspathEntry = new IClasspathEntry[] { entry };
|
||||
mContainerPath = path;
|
||||
mName = name;
|
||||
}
|
||||
|
||||
public IClasspathEntry[] getClasspathEntries() {
|
||||
@@ -44,7 +47,7 @@ class AndroidClasspathContainer implements IClasspathContainer {
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return "Android Library";
|
||||
return mName;
|
||||
}
|
||||
|
||||
public int getKind() {
|
||||
|
||||
@@ -16,10 +16,16 @@
|
||||
|
||||
package com.android.ide.eclipse.adt.project.internal;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtConstants;
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.project.ProjectHelper;
|
||||
import com.android.ide.eclipse.adt.sdk.LoadStatus;
|
||||
import com.android.ide.eclipse.adt.sdk.Sdk;
|
||||
import com.android.ide.eclipse.common.project.BaseProjectHelper;
|
||||
import com.android.sdklib.IAndroidTarget;
|
||||
|
||||
import org.eclipse.core.resources.IMarker;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.core.runtime.NullProgressMonitor;
|
||||
@@ -38,10 +44,6 @@ import org.eclipse.jdt.core.JavaModelException;
|
||||
* {@link IProject}s. This removes the hard-coded path to the android.jar.
|
||||
*/
|
||||
public class AndroidClasspathContainerInitializer extends ClasspathContainerInitializer {
|
||||
/** The old container id */
|
||||
private final static String OLD_CONTAINER_ID =
|
||||
"com.android.ide.eclipse.adt.project.AndroidClasspathContainerInitializer"; //$NON-NLS-1$
|
||||
|
||||
/** The container id for the android framework jar file */
|
||||
private final static String CONTAINER_ID =
|
||||
"com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"; //$NON-NLS-1$
|
||||
@@ -58,17 +60,10 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit
|
||||
*/
|
||||
@Override
|
||||
public void initialize(IPath containerPath, IJavaProject project) throws CoreException {
|
||||
String id = null;
|
||||
if (OLD_CONTAINER_ID.equals(containerPath.toString())) {
|
||||
id = OLD_CONTAINER_ID;
|
||||
} else if (CONTAINER_ID.equals(containerPath.toString())) {
|
||||
id = CONTAINER_ID;
|
||||
}
|
||||
|
||||
if (id != null) {
|
||||
JavaCore.setClasspathContainer(new Path(id),
|
||||
if (CONTAINER_ID.equals(containerPath.toString())) {
|
||||
JavaCore.setClasspathContainer(new Path(CONTAINER_ID),
|
||||
new IJavaProject[] { project },
|
||||
new IClasspathContainer[] { allocateAndroidContainer(id) },
|
||||
new IClasspathContainer[] { allocateAndroidContainer(CONTAINER_ID, project) },
|
||||
new NullProgressMonitor());
|
||||
}
|
||||
}
|
||||
@@ -81,15 +76,6 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit
|
||||
return JavaCore.newContainerEntry(new Path(CONTAINER_ID));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the {@link IPath} objects against the old android framework container id and
|
||||
* returns <code>true</code> if they are identical.
|
||||
* @param path the <code>IPath</code> to check.
|
||||
*/
|
||||
public static boolean checkOldPath(IPath path) {
|
||||
return OLD_CONTAINER_ID.equals(path.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the {@link IPath} objects against the android framework container id and
|
||||
* returns <code>true</code> if they are identical.
|
||||
@@ -106,41 +92,18 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit
|
||||
* @return <code>true</code> if success, <code>false</code> otherwise.
|
||||
*/
|
||||
public static boolean updateProjects(IJavaProject[] androidProjects) {
|
||||
|
||||
try {
|
||||
// because those projects could have the old id, we are going to fix
|
||||
// them dynamically here.
|
||||
for (IJavaProject javaProject: androidProjects) {
|
||||
IClasspathEntry[] entries = javaProject.getRawClasspath();
|
||||
|
||||
int containerIndex = ProjectHelper.findClasspathEntryByPath(entries,
|
||||
OLD_CONTAINER_ID,
|
||||
IClasspathEntry.CPE_CONTAINER);
|
||||
if (containerIndex != -1) {
|
||||
// the project has the old container, we remove it
|
||||
entries = ProjectHelper.removeEntryFromClasspath(entries, containerIndex);
|
||||
|
||||
// we add the new one instead
|
||||
entries = ProjectHelper.addEntryToClasspath(entries, getContainerEntry());
|
||||
|
||||
// and give the new entries to the project
|
||||
javaProject.setRawClasspath(entries, new NullProgressMonitor());
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate a new AndroidClasspathContainer, and associate it to the android framework
|
||||
// container id for each projects.
|
||||
// By providing a new association between a container id and a IClasspathContainer,
|
||||
// this forces the JDT to query the IClasspathContainer for new IClasspathEntry (with
|
||||
// IClasspathContainer#getClasspathEntries()), and therefore force recompilation of
|
||||
// the projects.
|
||||
// TODO: We could only do that for the projects haven't fixed above
|
||||
// (this isn't something that will happen a lot though)
|
||||
int projectCount = androidProjects.length;
|
||||
|
||||
IClasspathContainer[] containers = new IClasspathContainer[projectCount];
|
||||
for (int i = 0 ; i < projectCount; i++) {
|
||||
containers[i] = allocateAndroidContainer(CONTAINER_ID);
|
||||
containers[i] = allocateAndroidContainer(CONTAINER_ID, androidProjects[i]);
|
||||
}
|
||||
|
||||
// give each project their new container in one call.
|
||||
@@ -158,23 +121,123 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit
|
||||
* Allocates and returns an {@link AndroidClasspathContainer} object with the proper
|
||||
* path to the framework jar file.
|
||||
* @param containerId the container id to be used.
|
||||
* @param javaProject The java project that will receive the container.
|
||||
*/
|
||||
private static IClasspathContainer allocateAndroidContainer(String containerId) {
|
||||
return new AndroidClasspathContainer(createFrameworkClasspath(), new Path(containerId));
|
||||
private static IClasspathContainer allocateAndroidContainer(String containerId,
|
||||
IJavaProject javaProject) {
|
||||
IProject iProject = javaProject.getProject();
|
||||
|
||||
// remove potential MARKER_TARGETs.
|
||||
try {
|
||||
if (iProject.exists()) {
|
||||
iProject.deleteMarkers(AdtConstants.MARKER_TARGET, true,
|
||||
IResource.DEPTH_INFINITE);
|
||||
}
|
||||
} catch (CoreException ce) {
|
||||
// just log the error
|
||||
AdtPlugin.log(ce, "Error removing target marker.");
|
||||
}
|
||||
|
||||
|
||||
// first we check if the SDK has been loaded
|
||||
boolean sdkIsLoaded = AdtPlugin.getDefault().getSdkLoadStatus(javaProject) ==
|
||||
LoadStatus.LOADED;
|
||||
|
||||
// then we check if the project has a valid target.
|
||||
IAndroidTarget target = null;
|
||||
if (sdkIsLoaded) {
|
||||
target = Sdk.getCurrent().getTarget(iProject);
|
||||
}
|
||||
|
||||
// if we are loaded and the target is non null, we create a valid ClassPathContainer
|
||||
if (sdkIsLoaded && target != null) {
|
||||
String targetName = null;
|
||||
if (target.isPlatform()) {
|
||||
targetName = target.getName();
|
||||
} else {
|
||||
targetName = String.format("%1$s (%2$s)", target.getName(),
|
||||
target.getApiVersionName());
|
||||
}
|
||||
|
||||
return new AndroidClasspathContainer(createFrameworkClasspath(target),
|
||||
new Path(containerId), targetName);
|
||||
}
|
||||
|
||||
// else we put a marker on the project, and return a dummy container (to replace the
|
||||
// previous one if there was one.)
|
||||
|
||||
// Get the project's target's hash string (if it exists)
|
||||
String hashString = Sdk.getProjectTargetHashString(iProject);
|
||||
|
||||
String message = null;
|
||||
boolean outputToConsole = true;
|
||||
if (hashString == null || hashString.length() == 0) {
|
||||
message = String.format(
|
||||
"Project has no target set. Edit the project properties to set one.");
|
||||
} else if (sdkIsLoaded) {
|
||||
message = String.format(
|
||||
"Unable to resolve target '%s'", hashString);
|
||||
} else {
|
||||
// this is the case where there is a hashString but the SDK is not yet
|
||||
// loaded and therefore we can't get the target yet.
|
||||
message = String.format(
|
||||
"Unable to resolve target '%s' until the SDK is loaded.", hashString);
|
||||
|
||||
// let's not log this one to the console as it will happen at every boot,
|
||||
// and it's expected. (we do keep the error marker though).
|
||||
outputToConsole = false;
|
||||
}
|
||||
|
||||
// log the error and put the marker on the project
|
||||
if (outputToConsole) {
|
||||
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_ALWAYS, iProject, message);
|
||||
}
|
||||
IMarker marker = BaseProjectHelper.addMarker(iProject, AdtConstants.MARKER_TARGET, message,
|
||||
IMarker.SEVERITY_ERROR);
|
||||
|
||||
// add a marker priority as this is an more important error than the error that will
|
||||
// spring from the lack of library
|
||||
try {
|
||||
marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH);
|
||||
} catch (CoreException e) {
|
||||
// just log the error
|
||||
AdtPlugin.log(e, "Error changing target marker priority.");
|
||||
}
|
||||
|
||||
// return a dummy container to replace the one we may have had before.
|
||||
return new IClasspathContainer() {
|
||||
public IClasspathEntry[] getClasspathEntries() {
|
||||
return new IClasspathEntry[0];
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return "Unable to get system library for the project";
|
||||
}
|
||||
|
||||
public int getKind() {
|
||||
return IClasspathContainer.K_DEFAULT_SYSTEM;
|
||||
}
|
||||
|
||||
public IPath getPath() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a new {@link IClasspathEntry} object for the android framework.
|
||||
* <p/>This references the OS path to the android.jar and the java doc directory. This is
|
||||
* dynamically created when a project is opened, and never saved in the project itself, so
|
||||
* there's no risk of storing an obsolete path.
|
||||
* Creates and returns a new {@link IClasspathEntry} object for the android
|
||||
* framework. <p/>This references the OS path to the android.jar and the
|
||||
* java doc directory. This is dynamically created when a project is opened,
|
||||
* and never saved in the project itself, so there's no risk of storing an
|
||||
* obsolete path.
|
||||
*
|
||||
* @param target The target that contains the libraries.
|
||||
*/
|
||||
private static IClasspathEntry createFrameworkClasspath() {
|
||||
private static IClasspathEntry createFrameworkClasspath(IAndroidTarget target) {
|
||||
// now add the android framework to the class path.
|
||||
// create the path object.
|
||||
IPath android_lib = new Path(AdtPlugin.getOsAbsoluteFramework());
|
||||
|
||||
IPath android_src = new Path(AdtPlugin.getOsAbsoluteAndroidSources());
|
||||
IPath android_lib = new Path(target.getPath(IAndroidTarget.ANDROID_JAR));
|
||||
IPath android_src = new Path(target.getPath(IAndroidTarget.SOURCES));
|
||||
|
||||
// create the java doc link.
|
||||
IClasspathAttribute cpAttribute = JavaCore.newClasspathAttribute(
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.project.properties;
|
||||
|
||||
import com.android.ide.eclipse.adt.sdk.Sdk;
|
||||
import com.android.sdklib.IAndroidTarget;
|
||||
import com.android.sdkuilib.SdkTargetSelector;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.SelectionAdapter;
|
||||
import org.eclipse.swt.events.SelectionEvent;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.ui.IWorkbenchPropertyPage;
|
||||
import org.eclipse.ui.dialogs.PropertyPage;
|
||||
|
||||
/**
|
||||
* Property page for "Android" project.
|
||||
* This is accessible from the Package Explorer when right clicking a project and choosing
|
||||
* "Properties".
|
||||
*
|
||||
*/
|
||||
public class AndroidPropertyPage extends PropertyPage implements IWorkbenchPropertyPage {
|
||||
|
||||
private IProject mProject;
|
||||
private SdkTargetSelector mSelector;
|
||||
|
||||
public AndroidPropertyPage() {
|
||||
// pass
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Control createContents(Composite parent) {
|
||||
// get the element (this is not yet valid in the constructor).
|
||||
mProject = (IProject)getElement();
|
||||
|
||||
Composite top = new Composite(parent, SWT.NONE);
|
||||
top.setLayoutData(new GridData(GridData.FILL_BOTH));
|
||||
top.setLayout(new GridLayout(1, false));
|
||||
|
||||
Label l = new Label(top, SWT.NONE);
|
||||
l.setText("Project Target");
|
||||
|
||||
// get the targets from the sdk
|
||||
IAndroidTarget[] targets = null;
|
||||
if (Sdk.getCurrent() != null) {
|
||||
targets = Sdk.getCurrent().getTargets();
|
||||
}
|
||||
|
||||
// build the UI.
|
||||
mSelector = new SdkTargetSelector(top, targets, false /*allowMultipleSelection*/);
|
||||
|
||||
if (Sdk.getCurrent() != null) {
|
||||
IAndroidTarget target = Sdk.getCurrent().getTarget(mProject);
|
||||
if (target != null) {
|
||||
mSelector.setSelection(target);
|
||||
}
|
||||
}
|
||||
|
||||
mSelector.setSelectionListener(new SelectionAdapter() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent e) {
|
||||
// look for the selection and validate the page if there is a selection
|
||||
IAndroidTarget target = mSelector.getFirstSelected();
|
||||
setValid(target != null);
|
||||
}
|
||||
});
|
||||
|
||||
return top;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performOk() {
|
||||
if (Sdk.getCurrent() != null) {
|
||||
Sdk.getCurrent().setProject(mProject, mSelector.getFirstSelected());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.resources;
|
||||
package com.android.ide.eclipse.adt.sdk;
|
||||
|
||||
import com.android.ide.eclipse.adt.resources.LayoutParamsParser.IClass;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
@@ -35,9 +34,12 @@ import javax.management.InvalidAttributeValueException;
|
||||
/**
|
||||
* Custom class loader able to load a class from the SDK jar file.
|
||||
*/
|
||||
public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
|
||||
public class AndroidJarLoader extends ClassLoader implements IAndroidClassLoader {
|
||||
|
||||
public final static class ClassWrapper implements IClass {
|
||||
/**
|
||||
* Wrapper around a {@link Class} to provide the methods of {@link IClassDescriptor}.
|
||||
*/
|
||||
public final static class ClassWrapper implements IClassDescriptor {
|
||||
private Class<?> mClass;
|
||||
|
||||
public ClassWrapper(Class<?> clazz) {
|
||||
@@ -48,9 +50,9 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
|
||||
return mClass.getCanonicalName();
|
||||
}
|
||||
|
||||
public IClass[] getDeclaredClasses() {
|
||||
public IClassDescriptor[] getDeclaredClasses() {
|
||||
Class<?>[] classes = mClass.getDeclaredClasses();
|
||||
IClass[] iclasses = new IClass[classes.length];
|
||||
IClassDescriptor[] iclasses = new IClassDescriptor[classes.length];
|
||||
for (int i = 0 ; i < classes.length ; i++) {
|
||||
iclasses[i] = new ClassWrapper(classes[i]);
|
||||
}
|
||||
@@ -58,7 +60,7 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
|
||||
return iclasses;
|
||||
}
|
||||
|
||||
public IClass getEnclosingClass() {
|
||||
public IClassDescriptor getEnclosingClass() {
|
||||
return new ClassWrapper(mClass.getEnclosingClass());
|
||||
}
|
||||
|
||||
@@ -66,7 +68,7 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
|
||||
return mClass.getSimpleName();
|
||||
}
|
||||
|
||||
public IClass getSuperclass() {
|
||||
public IClassDescriptor getSuperclass() {
|
||||
return new ClassWrapper(mClass.getSuperclass());
|
||||
}
|
||||
|
||||
@@ -131,17 +133,18 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
|
||||
* @param packageFilter The package that contains all the class data to preload, using a fully
|
||||
* qualified binary name (.e.g "com.my.package."). The matching algorithm
|
||||
* is simple "startsWith". Use an empty string to include everything.
|
||||
* @param taskLabel An optional task name for the sub monitor. Can be null.
|
||||
* @param monitor A progress monitor. Can be null. Caller is responsible for calling done.
|
||||
* @throws IOException
|
||||
* @throws InvalidAttributeValueException
|
||||
* @throws ClassFormatError
|
||||
*/
|
||||
public void preLoadClasses(String packageFilter, IProgressMonitor monitor)
|
||||
public void preLoadClasses(String packageFilter, String taskLabel, IProgressMonitor monitor)
|
||||
throws IOException, InvalidAttributeValueException, ClassFormatError {
|
||||
// Transform the package name into a zip entry path
|
||||
String pathFilter = packageFilter.replaceAll("\\.", "/"); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
|
||||
SubMonitor progress = SubMonitor.convert(monitor, 100);
|
||||
SubMonitor progress = SubMonitor.convert(monitor, taskLabel == null ? "" : taskLabel, 100);
|
||||
|
||||
// create streams to read the intermediary archive
|
||||
FileInputStream fis = new FileInputStream(mOsFrameworkLocation);
|
||||
@@ -174,6 +177,7 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
|
||||
// advance 5% of whatever is allocated on the progress bar
|
||||
progress.setWorkRemaining(100);
|
||||
progress.worked(5);
|
||||
progress.subTask(String.format("Preload %1$s", className));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,17 +197,18 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
|
||||
* @throws InvalidAttributeValueException
|
||||
* @throws ClassFormatError
|
||||
*/
|
||||
public HashMap<String, ArrayList<IClass>> findClassesDerivingFrom(
|
||||
public HashMap<String, ArrayList<IClassDescriptor>> findClassesDerivingFrom(
|
||||
String packageFilter,
|
||||
String[] superClasses)
|
||||
throws IOException, InvalidAttributeValueException, ClassFormatError {
|
||||
|
||||
packageFilter = packageFilter.replaceAll("\\.", "/"); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
|
||||
HashMap<String, ArrayList<IClass>> mClassesFound = new HashMap<String, ArrayList<IClass>>();
|
||||
HashMap<String, ArrayList<IClassDescriptor>> mClassesFound =
|
||||
new HashMap<String, ArrayList<IClassDescriptor>>();
|
||||
|
||||
for (String className : superClasses) {
|
||||
mClassesFound.put(className, new ArrayList<IClass>());
|
||||
mClassesFound.put(className, new ArrayList<IClassDescriptor>());
|
||||
}
|
||||
|
||||
// create streams to read the intermediary archive
|
||||
@@ -415,7 +420,7 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
|
||||
* @param className the fully-qualified name of the class to return.
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public IClass getClass(String className) throws ClassNotFoundException {
|
||||
public IClassDescriptor getClass(String className) throws ClassNotFoundException {
|
||||
try {
|
||||
return new ClassWrapper(loadClass(className));
|
||||
} catch (ClassNotFoundException e) {
|
||||
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.sdk;
|
||||
|
||||
import com.android.ide.eclipse.common.resources.IResourceRepository;
|
||||
import com.android.ide.eclipse.editors.descriptors.IDescriptorProvider;
|
||||
import com.android.ide.eclipse.editors.layout.descriptors.LayoutDescriptors;
|
||||
import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors;
|
||||
import com.android.ide.eclipse.editors.menu.descriptors.MenuDescriptors;
|
||||
import com.android.ide.eclipse.editors.resources.descriptors.ResourcesDescriptors;
|
||||
import com.android.ide.eclipse.editors.resources.manager.ProjectResources;
|
||||
import com.android.ide.eclipse.editors.xml.descriptors.XmlDescriptors;
|
||||
import com.android.layoutlib.api.ILayoutBridge;
|
||||
import com.android.sdklib.IAndroidTarget;
|
||||
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This class contains the data of an Android Target as loaded from the SDK.
|
||||
*/
|
||||
public class AndroidTargetData {
|
||||
|
||||
public final static int DESCRIPTOR_MANIFEST = 1;
|
||||
public final static int DESCRIPTOR_LAYOUT = 2;
|
||||
public final static int DESCRIPTOR_MENU = 3;
|
||||
public final static int DESCRIPTOR_XML = 4;
|
||||
public final static int DESCRIPTOR_RESOURCES = 5;
|
||||
public final static int DESCRIPTOR_SEARCHABLE = 6;
|
||||
public final static int DESCRIPTOR_PREFERENCES = 7;
|
||||
|
||||
public final static class LayoutBridge {
|
||||
/** Link to the layout bridge */
|
||||
public ILayoutBridge bridge;
|
||||
|
||||
public LoadStatus status = LoadStatus.LOADING;
|
||||
|
||||
public ClassLoader classLoader;
|
||||
}
|
||||
|
||||
private final IAndroidTarget mTarget;
|
||||
|
||||
/**
|
||||
* mAttributeValues is a map { key => list [ values ] }.
|
||||
* The key for the map is "(element-xml-name,attribute-namespace:attribute-xml-local-name)".
|
||||
* The attribute namespace prefix must be:
|
||||
* - "android" for AndroidConstants.NS_RESOURCES
|
||||
* - "xmlns" for the XMLNS URI.
|
||||
*
|
||||
* This is used for attributes that do not have a unique name, but still need to be populated
|
||||
* with values in the UI. Uniquely named attributes have their values in {@link #mEnumValueMap}.
|
||||
*/
|
||||
private final Hashtable<String, String[]> mAttributeValues = new Hashtable<String, String[]>();
|
||||
|
||||
private IResourceRepository mSystemResourceRepository;
|
||||
|
||||
private final AndroidManifestDescriptors mManifestDescriptors;
|
||||
private final LayoutDescriptors mLayoutDescriptors;
|
||||
private final MenuDescriptors mMenuDescriptors;
|
||||
private final XmlDescriptors mXmlDescriptors;
|
||||
|
||||
private final Map<String, Map<String, Integer>> mEnumValueMap;
|
||||
|
||||
private final ProjectResources mFrameworkResources;
|
||||
private final LayoutBridge mLayoutBridge;
|
||||
|
||||
private boolean mLayoutBridgeInit = false;
|
||||
|
||||
/**
|
||||
* Creates an AndroidTargetData object.
|
||||
*/
|
||||
AndroidTargetData(IAndroidTarget androidTarget,
|
||||
IResourceRepository systemResourceRepository,
|
||||
AndroidManifestDescriptors manifestDescriptors,
|
||||
LayoutDescriptors layoutDescriptors,
|
||||
MenuDescriptors menuDescriptors,
|
||||
XmlDescriptors xmlDescriptors,
|
||||
Map<String, Map<String, Integer>> enumValueMap,
|
||||
String[] permissionValues,
|
||||
String[] activityIntentActionValues,
|
||||
String[] broadcastIntentActionValues,
|
||||
String[] serviceIntentActionValues,
|
||||
String[] intentCategoryValues,
|
||||
ProjectResources resources,
|
||||
LayoutBridge layoutBridge) {
|
||||
|
||||
mTarget = androidTarget;
|
||||
mSystemResourceRepository = systemResourceRepository;
|
||||
mManifestDescriptors = manifestDescriptors;
|
||||
mLayoutDescriptors = layoutDescriptors;
|
||||
mMenuDescriptors = menuDescriptors;
|
||||
mXmlDescriptors = xmlDescriptors;
|
||||
mEnumValueMap = enumValueMap;
|
||||
mFrameworkResources = resources;
|
||||
mLayoutBridge = layoutBridge;
|
||||
|
||||
setPermissions(permissionValues);
|
||||
setIntentFilterActionsAndCategories(activityIntentActionValues, broadcastIntentActionValues,
|
||||
serviceIntentActionValues, intentCategoryValues);
|
||||
}
|
||||
|
||||
public IResourceRepository getSystemResources() {
|
||||
return mSystemResourceRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link IDescriptorProvider} from a given Id.
|
||||
* The Id can be one of {@link #DESCRIPTOR_MANIFEST}, {@link #DESCRIPTOR_LAYOUT},
|
||||
* {@link #DESCRIPTOR_MENU}, or {@link #DESCRIPTOR_XML}.
|
||||
* All other values will throw an {@link IllegalArgumentException}.
|
||||
*/
|
||||
public IDescriptorProvider getDescriptorProvider(int descriptorId) {
|
||||
switch (descriptorId) {
|
||||
case DESCRIPTOR_MANIFEST:
|
||||
return mManifestDescriptors;
|
||||
case DESCRIPTOR_LAYOUT:
|
||||
return mLayoutDescriptors;
|
||||
case DESCRIPTOR_MENU:
|
||||
return mMenuDescriptors;
|
||||
case DESCRIPTOR_XML:
|
||||
return mXmlDescriptors;
|
||||
case DESCRIPTOR_RESOURCES:
|
||||
// FIXME: since it's hard-coded the Resources Descriptors are not platform dependent.
|
||||
return ResourcesDescriptors.getInstance();
|
||||
case DESCRIPTOR_PREFERENCES:
|
||||
return mXmlDescriptors.getPreferencesProvider();
|
||||
case DESCRIPTOR_SEARCHABLE:
|
||||
return mXmlDescriptors.getSearchableProvider();
|
||||
default :
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the manifest descriptors.
|
||||
*/
|
||||
public AndroidManifestDescriptors getManifestDescriptors() {
|
||||
return mManifestDescriptors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the layout Descriptors.
|
||||
*/
|
||||
public LayoutDescriptors getLayoutDescriptors() {
|
||||
return mLayoutDescriptors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the menu descriptors.
|
||||
*/
|
||||
public MenuDescriptors getMenuDescriptors() {
|
||||
return mMenuDescriptors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the XML descriptors
|
||||
*/
|
||||
public XmlDescriptors getXmlDescriptors() {
|
||||
return mXmlDescriptors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this list of possible values for an XML attribute.
|
||||
* <p/>This should only be called for attributes for which possible values depend on the
|
||||
* parent element node.
|
||||
* <p/>For attributes that have the same values no matter the parent node, use
|
||||
* {@link #getEnumValueMap()}.
|
||||
* @param elementName the name of the element containing the attribute.
|
||||
* @param attributeName the name of the attribute
|
||||
* @return an array of String with the possible values, or <code>null</code> if no values were
|
||||
* found.
|
||||
*/
|
||||
public String[] getAttributeValues(String elementName, String attributeName) {
|
||||
String key = String.format("(%1$s,%2$s)", elementName, attributeName); //$NON-NLS-1$
|
||||
return mAttributeValues.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this list of possible values for an XML attribute.
|
||||
* <p/>This should only be called for attributes for which possible values depend on the
|
||||
* parent and great-grand-parent element node.
|
||||
* <p/>The typical example of this is for the 'name' attribute under
|
||||
* activity/intent-filter/action
|
||||
* <p/>For attributes that have the same values no matter the parent node, use
|
||||
* {@link #getEnumValueMap()}.
|
||||
* @param elementName the name of the element containing the attribute.
|
||||
* @param attributeName the name of the attribute
|
||||
* @param greatGrandParentElementName the great-grand-parent node.
|
||||
* @return an array of String with the possible values, or <code>null</code> if no values were
|
||||
* found.
|
||||
*/
|
||||
public String[] getAttributeValues(String elementName, String attributeName,
|
||||
String greatGrandParentElementName) {
|
||||
if (greatGrandParentElementName != null) {
|
||||
String key = String.format("(%1$s,%2$s,%3$s)", //$NON-NLS-1$
|
||||
greatGrandParentElementName, elementName, attributeName);
|
||||
String[] values = mAttributeValues.get(key);
|
||||
if (values != null) {
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
return getAttributeValues(elementName, attributeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the enum values map.
|
||||
* <p/>The map defines the possible values for XML attributes. The key is the attribute name
|
||||
* and the value is a map of (string, integer) in which the key (string) is the name of
|
||||
* the value, and the Integer is the numerical value in the compiled binary XML files.
|
||||
*/
|
||||
public Map<String, Map<String, Integer>> getEnumValueMap() {
|
||||
return mEnumValueMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ProjectResources} containing the Framework Resources.
|
||||
*/
|
||||
public ProjectResources getFrameworkResources() {
|
||||
return mFrameworkResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link LayoutBridge} object possibly containing a {@link ILayoutBridge} object.
|
||||
* <p/>If {@link LayoutBridge#bridge} is <code>null</code>, {@link LayoutBridge#status} will
|
||||
* contain the reason (either {@link LoadStatus#LOADING} or {@link LoadStatus#FAILED}).
|
||||
* <p/>Valid {@link ILayoutBridge} objects are always initialized before being returned.
|
||||
*/
|
||||
public synchronized LayoutBridge getLayoutBridge() {
|
||||
if (mLayoutBridgeInit == false && mLayoutBridge.bridge != null) {
|
||||
mLayoutBridge.bridge.init(mTarget.getPath(IAndroidTarget.FONTS),
|
||||
getEnumValueMap());
|
||||
mLayoutBridgeInit = true;
|
||||
}
|
||||
return mLayoutBridge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the permission values
|
||||
* @param permissionValues the list of permissions
|
||||
*/
|
||||
private void setPermissions(String[] permissionValues) {
|
||||
setValues("(uses-permission,android:name)", permissionValues); //$NON-NLS-1$
|
||||
setValues("(application,android:permission)", permissionValues); //$NON-NLS-1$
|
||||
setValues("(activity,android:permission)", permissionValues); //$NON-NLS-1$
|
||||
setValues("(receiver,android:permission)", permissionValues); //$NON-NLS-1$
|
||||
setValues("(service,android:permission)", permissionValues); //$NON-NLS-1$
|
||||
setValues("(provider,android:permission)", permissionValues); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
private void setIntentFilterActionsAndCategories(String[] activityIntentActions,
|
||||
String[] broadcastIntentActions, String[] serviceIntentActions,
|
||||
String[] intentCategoryValues) {
|
||||
setValues("(activity,action,android:name)", activityIntentActions); //$NON-NLS-1$
|
||||
setValues("(receiver,action,android:name)", broadcastIntentActions); //$NON-NLS-1$
|
||||
setValues("(service,action,android:name)", serviceIntentActions); //$NON-NLS-1$
|
||||
setValues("(category,android:name)", intentCategoryValues); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a (name, values) pair in the hash map.
|
||||
* <p/>
|
||||
* If the name is already present in the map, it is first removed.
|
||||
* @param name the name associated with the values.
|
||||
* @param values The values to add.
|
||||
*/
|
||||
private void setValues(String name, String[] values) {
|
||||
mAttributeValues.remove(name);
|
||||
mAttributeValues.put(name, values);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,19 +14,29 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.resources;
|
||||
package com.android.ide.eclipse.adt.sdk;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.sdk.AndroidTargetData.LayoutBridge;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.common.resources.AttrsXmlParser;
|
||||
import com.android.ide.eclipse.common.resources.DeclareStyleableInfo;
|
||||
import com.android.ide.eclipse.common.resources.FrameworkResourceManager;
|
||||
import com.android.ide.eclipse.common.resources.IResourceRepository;
|
||||
import com.android.ide.eclipse.common.resources.ResourceItem;
|
||||
import com.android.ide.eclipse.common.resources.ResourceType;
|
||||
import com.android.ide.eclipse.common.resources.ViewClassInfo;
|
||||
import com.android.ide.eclipse.editors.layout.descriptors.LayoutDescriptors;
|
||||
import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors;
|
||||
import com.android.ide.eclipse.editors.menu.descriptors.MenuDescriptors;
|
||||
import com.android.ide.eclipse.editors.resources.manager.ProjectResources;
|
||||
import com.android.ide.eclipse.editors.resources.manager.ResourceManager;
|
||||
import com.android.ide.eclipse.editors.xml.descriptors.XmlDescriptors;
|
||||
import com.android.layoutlib.api.ILayoutBridge;
|
||||
import com.android.sdklib.IAndroidTarget;
|
||||
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.eclipse.core.runtime.SubMonitor;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
@@ -34,10 +44,11 @@ import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -48,7 +59,7 @@ import java.util.Map;
|
||||
import javax.management.InvalidAttributeValueException;
|
||||
|
||||
/**
|
||||
* Parser for the framework library.
|
||||
* Parser for the platform data in an SDK.
|
||||
* <p/>
|
||||
* This gather the following information:
|
||||
* <ul>
|
||||
@@ -57,14 +68,16 @@ import javax.management.InvalidAttributeValueException;
|
||||
* <li></li>
|
||||
* </ul>
|
||||
*/
|
||||
public final class FrameworkResourceParser {
|
||||
public final class AndroidTargetParser {
|
||||
|
||||
private static final String TAG = "Framework Resource Parser";
|
||||
private final IAndroidTarget mAndroidTarget;
|
||||
|
||||
/**
|
||||
* Creates a framework resource parser.
|
||||
* Creates a platform data parser.
|
||||
*/
|
||||
public FrameworkResourceParser() {
|
||||
public AndroidTargetParser(IAndroidTarget platformTarget) {
|
||||
mAndroidTarget = platformTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,34 +90,29 @@ public final class FrameworkResourceParser {
|
||||
* @param monitor A progress monitor. Can be null. Caller is responsible for calling done.
|
||||
* @return True if the SDK path was valid and parsing has been attempted.
|
||||
*/
|
||||
public boolean parse(String osSdkPath, FrameworkResourceManager resourceManager,
|
||||
IProgressMonitor monitor) {
|
||||
if (osSdkPath == null || osSdkPath.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public IStatus run(IProgressMonitor monitor) {
|
||||
try {
|
||||
SubMonitor progress = SubMonitor.convert(monitor, 100);
|
||||
SubMonitor progress = SubMonitor.convert(monitor,
|
||||
String.format("Parsing SDK %1$s", mAndroidTarget.getName()),
|
||||
120);
|
||||
|
||||
AndroidJarLoader classLoader =
|
||||
new AndroidJarLoader(osSdkPath + AndroidConstants.FN_FRAMEWORK_LIBRARY);
|
||||
new AndroidJarLoader(mAndroidTarget.getPath(IAndroidTarget.ANDROID_JAR));
|
||||
|
||||
progress.subTask("Preloading");
|
||||
preload(classLoader, progress.newChild(40));
|
||||
progress.setWorkRemaining(60);
|
||||
preload(classLoader, progress.newChild(40, SubMonitor.SUPPRESS_NONE));
|
||||
progress.setWorkRemaining(80);
|
||||
|
||||
if (progress.isCanceled()) {
|
||||
return false;
|
||||
return Status.CANCEL_STATUS;
|
||||
}
|
||||
|
||||
// get the resource Ids.
|
||||
progress.subTask("Resource IDs");
|
||||
FrameworkResourceRepository systemResourceRepository = new FrameworkResourceRepository(
|
||||
collectResourceIds(classLoader));
|
||||
IResourceRepository frameworkRepository = collectResourceIds(classLoader);
|
||||
progress.worked(5);
|
||||
|
||||
if (progress.isCanceled()) {
|
||||
return false;
|
||||
return Status.CANCEL_STATUS;
|
||||
}
|
||||
|
||||
// get the permissions
|
||||
@@ -113,56 +121,59 @@ public final class FrameworkResourceParser {
|
||||
progress.worked(5);
|
||||
|
||||
if (progress.isCanceled()) {
|
||||
return false;
|
||||
return Status.CANCEL_STATUS;
|
||||
}
|
||||
|
||||
String osLibPath = osSdkPath + AndroidConstants.OS_SDK_LIBS_FOLDER;
|
||||
|
||||
// get the action and category values for the Intents.
|
||||
progress.subTask("Intents");
|
||||
ArrayList<String> activity_actions = new ArrayList<String>();
|
||||
ArrayList<String> broadcast_actions = new ArrayList<String>();
|
||||
ArrayList<String> service_actions = new ArrayList<String>();
|
||||
ArrayList<String> categories = new ArrayList<String>();
|
||||
collectIntentFilterActionsAndCategories(osLibPath,
|
||||
activity_actions, broadcast_actions, service_actions, categories);
|
||||
collectIntentFilterActionsAndCategories(activity_actions, broadcast_actions,
|
||||
service_actions, categories);
|
||||
progress.worked(5);
|
||||
|
||||
if (progress.isCanceled()) {
|
||||
return false;
|
||||
return Status.CANCEL_STATUS;
|
||||
}
|
||||
|
||||
progress.subTask("Layouts");
|
||||
// gather the attribute definition
|
||||
progress.subTask("Attributes definitions");
|
||||
AttrsXmlParser attrsXmlParser = new AttrsXmlParser(
|
||||
osSdkPath + AndroidConstants.OS_SDK_ATTRS_XML);
|
||||
mAndroidTarget.getPath(IAndroidTarget.ATTRIBUTES));
|
||||
attrsXmlParser.preload();
|
||||
|
||||
progress.subTask("Manifest definitions");
|
||||
AttrsXmlParser attrsManifestXmlParser = new AttrsXmlParser(
|
||||
osSdkPath + AndroidConstants.OS_SDK_ATTRS_MANIFEST_XML,
|
||||
mAndroidTarget.getPath(IAndroidTarget.MANIFEST_ATTRIBUTES),
|
||||
attrsXmlParser);
|
||||
attrsManifestXmlParser.preload();
|
||||
|
||||
Collection<ViewClassInfo> mainList = new ArrayList<ViewClassInfo>();
|
||||
Collection<ViewClassInfo> groupList = new ArrayList<ViewClassInfo>();
|
||||
|
||||
collectLayoutClasses(osLibPath, classLoader, attrsXmlParser, mainList, groupList,
|
||||
// collect the layout/widgets classes
|
||||
progress.subTask("Widgets and layouts");
|
||||
collectLayoutClasses(classLoader, attrsXmlParser, mainList, groupList,
|
||||
progress.newChild(40));
|
||||
|
||||
if (progress.isCanceled()) {
|
||||
return false;
|
||||
return Status.CANCEL_STATUS;
|
||||
}
|
||||
|
||||
ViewClassInfo[] layoutViewsInfo = mainList.toArray(new ViewClassInfo[mainList.size()]);
|
||||
ViewClassInfo[] layoutGroupsInfo = groupList.toArray(
|
||||
new ViewClassInfo[groupList.size()]);
|
||||
|
||||
// collect the preferences classes.
|
||||
mainList.clear();
|
||||
groupList.clear();
|
||||
collectPreferenceClasses(classLoader, attrsXmlParser, mainList, groupList,
|
||||
progress.newChild(5));
|
||||
|
||||
if (progress.isCanceled()) {
|
||||
return false;
|
||||
return Status.CANCEL_STATUS;
|
||||
}
|
||||
|
||||
ViewClassInfo[] preferencesInfo = mainList.toArray(new ViewClassInfo[mainList.size()]);
|
||||
@@ -177,34 +188,77 @@ public final class FrameworkResourceParser {
|
||||
Map<String, Map<String, Integer>> enumValueMap = attrsXmlParser.getEnumFlagValues();
|
||||
|
||||
if (progress.isCanceled()) {
|
||||
return false;
|
||||
return Status.CANCEL_STATUS;
|
||||
}
|
||||
|
||||
String docBaseUrl = getDocumentationBaseUrl(
|
||||
osSdkPath + AndroidConstants.OS_SDK_DOCS_FOLDER);
|
||||
// From the information that was collected, create the pieces that will be put in
|
||||
// the PlatformData object.
|
||||
AndroidManifestDescriptors manifestDescriptors = new AndroidManifestDescriptors();
|
||||
manifestDescriptors.updateDescriptors(manifestMap);
|
||||
progress.worked(10);
|
||||
|
||||
FrameworkResourceManager.getInstance().setResources(systemResourceRepository,
|
||||
layoutViewsInfo,
|
||||
layoutGroupsInfo,
|
||||
preferencesInfo,
|
||||
preferenceGroupsInfo,
|
||||
xmlMenuMap,
|
||||
xmlSearchableMap,
|
||||
manifestMap,
|
||||
if (progress.isCanceled()) {
|
||||
return Status.CANCEL_STATUS;
|
||||
}
|
||||
|
||||
LayoutDescriptors layoutDescriptors = new LayoutDescriptors();
|
||||
layoutDescriptors.updateDescriptors(layoutViewsInfo, layoutGroupsInfo);
|
||||
progress.worked(10);
|
||||
|
||||
if (progress.isCanceled()) {
|
||||
return Status.CANCEL_STATUS;
|
||||
}
|
||||
|
||||
MenuDescriptors menuDescriptors = new MenuDescriptors();
|
||||
menuDescriptors.updateDescriptors(xmlMenuMap);
|
||||
progress.worked(10);
|
||||
|
||||
if (progress.isCanceled()) {
|
||||
return Status.CANCEL_STATUS;
|
||||
}
|
||||
|
||||
XmlDescriptors xmlDescriptors = new XmlDescriptors();
|
||||
xmlDescriptors.updateDescriptors(xmlSearchableMap, preferencesInfo,
|
||||
preferenceGroupsInfo);
|
||||
progress.worked(10);
|
||||
|
||||
// load the framework resources.
|
||||
ProjectResources resources = ResourceManager.getInstance().loadFrameworkResources(
|
||||
mAndroidTarget);
|
||||
progress.worked(10);
|
||||
|
||||
// now load the layout lib bridge
|
||||
LayoutBridge layoutBridge = loadLayoutBridge();
|
||||
progress.worked(10);
|
||||
|
||||
// and finally create the PlatformData with all that we loaded.
|
||||
AndroidTargetData targetData = new AndroidTargetData(mAndroidTarget,
|
||||
frameworkRepository,
|
||||
manifestDescriptors,
|
||||
layoutDescriptors,
|
||||
menuDescriptors,
|
||||
xmlDescriptors,
|
||||
enumValueMap,
|
||||
permissionValues,
|
||||
activity_actions.toArray(new String[activity_actions.size()]),
|
||||
broadcast_actions.toArray(new String[broadcast_actions.size()]),
|
||||
service_actions.toArray(new String[service_actions.size()]),
|
||||
categories.toArray(new String[categories.size()]),
|
||||
docBaseUrl);
|
||||
resources,
|
||||
layoutBridge);
|
||||
|
||||
Sdk.getCurrent().setTargetData(mAndroidTarget, targetData);
|
||||
|
||||
return true;
|
||||
return Status.OK_STATUS;
|
||||
} catch (Exception e) {
|
||||
AdtPlugin.logAndPrintError(e, TAG, "SDK parser failed"); //$NON-NLS-1$
|
||||
AdtPlugin.printToConsole("SDK parser failed", e.getMessage());
|
||||
return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, "SDK parser failed", e);
|
||||
} finally {
|
||||
if (monitor != null) {
|
||||
monitor.done();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,7 +271,9 @@ public final class FrameworkResourceParser {
|
||||
*/
|
||||
private void preload(AndroidJarLoader classLoader, IProgressMonitor monitor) {
|
||||
try {
|
||||
classLoader.preLoadClasses("" /* all classes */, monitor); //$NON-NLS-1$
|
||||
classLoader.preLoadClasses("" /* all classes */, //$NON-NLS-1$
|
||||
mAndroidTarget.getName(), // monitor task label
|
||||
monitor);
|
||||
} catch (InvalidAttributeValueException e) {
|
||||
AdtPlugin.log(e, "Problem preloading classes"); //$NON-NLS-1$
|
||||
} catch (IOException e) {
|
||||
@@ -226,24 +282,27 @@ public final class FrameworkResourceParser {
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects the resources IDs found in the SDK.
|
||||
* Creates an IResourceRepository for the framework resources.
|
||||
*
|
||||
* @param classLoader The framework SDK jar classloader
|
||||
* @return a map of the resources, or null if it failed.
|
||||
*/
|
||||
private Map<ResourceType, List<ResourceItem>> collectResourceIds(
|
||||
private IResourceRepository collectResourceIds(
|
||||
AndroidJarLoader classLoader) {
|
||||
try {
|
||||
Class<?> r = classLoader.loadClass(AndroidConstants.CLASS_R);
|
||||
|
||||
if (r != null) {
|
||||
return parseRClass(r);
|
||||
Map<ResourceType, List<ResourceItem>> map = parseRClass(r);
|
||||
if (map != null) {
|
||||
return new FrameworkResourceRepository(map);
|
||||
}
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
AdtPlugin.logAndPrintError(e, TAG,
|
||||
"Collect resource IDs failed, class %1$s not found in %2$s", //$NON-NLS-1$
|
||||
AndroidConstants.CLASS_R,
|
||||
classLoader.getSource());
|
||||
mAndroidTarget.getPath(IAndroidTarget.ANDROID_JAR));
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -331,7 +390,7 @@ public final class FrameworkResourceParser {
|
||||
AdtPlugin.logAndPrintError(e, TAG,
|
||||
"Collect permissions failed, class %1$s not found in %2$s", //$NON-NLS-1$
|
||||
AndroidConstants.CLASS_MANIFEST_PERMISSION,
|
||||
classLoader.getSource());
|
||||
mAndroidTarget.getPath(IAndroidTarget.ANDROID_JAR));
|
||||
}
|
||||
|
||||
return new String[0];
|
||||
@@ -347,13 +406,17 @@ public final class FrameworkResourceParser {
|
||||
* @param serviceActions the list which will receive the service action values.
|
||||
* @param categories the list which will receive the category values.
|
||||
*/
|
||||
private void collectIntentFilterActionsAndCategories(String osLibPath,
|
||||
ArrayList<String> activityActions, ArrayList<String> broadcastActions,
|
||||
private void collectIntentFilterActionsAndCategories(ArrayList<String> activityActions,
|
||||
ArrayList<String> broadcastActions,
|
||||
ArrayList<String> serviceActions, ArrayList<String> categories) {
|
||||
collectValues(osLibPath + "activity_actions.txt" , activityActions);
|
||||
collectValues(osLibPath + "broadcast_actions.txt" , broadcastActions);
|
||||
collectValues(osLibPath + "service_actions.txt" , serviceActions);
|
||||
collectValues(osLibPath + "categories.txt" , categories);
|
||||
collectValues(mAndroidTarget.getPath(IAndroidTarget.ACTIONS_ACTIVITY),
|
||||
activityActions);
|
||||
collectValues(mAndroidTarget.getPath(IAndroidTarget.ACTIONS_BROADCAST),
|
||||
broadcastActions);
|
||||
collectValues(mAndroidTarget.getPath(IAndroidTarget.ACTIONS_SERVICE),
|
||||
serviceActions);
|
||||
collectValues(mAndroidTarget.getPath(IAndroidTarget.CATEGORIES),
|
||||
categories);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -400,21 +463,21 @@ public final class FrameworkResourceParser {
|
||||
* Collects all layout classes information from the class loader and the
|
||||
* attrs.xml and sets the corresponding structures in the resource manager.
|
||||
*
|
||||
* @param osLibPath The OS path to the SDK tools/lib folder, ending with a separator.
|
||||
* @param classLoader The framework SDK jar classloader
|
||||
* @param classLoader The framework SDK jar classloader in case we cannot get the widget from
|
||||
* the platform directly
|
||||
* @param attrsXmlParser The parser of the attrs.xml file
|
||||
* @param mainList the Collection to receive the main list of {@link ViewClassInfo}.
|
||||
* @param groupList the Collection to receive the group list of {@link ViewClassInfo}.
|
||||
* @param monitor A progress monitor. Can be null. Caller is responsible for calling done.
|
||||
*/
|
||||
private void collectLayoutClasses(String osLibPath,
|
||||
AndroidJarLoader classLoader,
|
||||
private void collectLayoutClasses(AndroidJarLoader classLoader,
|
||||
AttrsXmlParser attrsXmlParser,
|
||||
Collection<ViewClassInfo> mainList, Collection<ViewClassInfo> groupList,
|
||||
IProgressMonitor monitor) {
|
||||
LayoutParamsParser ldp = null;
|
||||
try {
|
||||
WidgetListLoader loader = new WidgetListLoader(osLibPath + "widgets.txt");
|
||||
WidgetClassLoader loader = new WidgetClassLoader(
|
||||
mAndroidTarget.getPath(IAndroidTarget.WIDGETS));
|
||||
if (loader.parseWidgetList(monitor)) {
|
||||
ldp = new LayoutParamsParser(loader, attrsXmlParser);
|
||||
}
|
||||
@@ -465,12 +528,13 @@ public final class FrameworkResourceParser {
|
||||
}
|
||||
} catch (NoClassDefFoundError e) {
|
||||
AdtPlugin.logAndPrintError(e, TAG,
|
||||
"Collect preferences failed, class %1$s not found in %2$s", //$NON-NLS-1$
|
||||
"Collect preferences failed, class %1$s not found in %2$s",
|
||||
e.getMessage(),
|
||||
classLoader.getSource());
|
||||
} catch (Throwable e) {
|
||||
AdtPlugin.log(e, "Android Framework Parser: failed to collect preference classes"); //$NON-NLS-1$
|
||||
AdtPlugin.printErrorToConsole("Android Framework Parser", "failed to collect preference classes");
|
||||
AdtPlugin.printErrorToConsole("Android Framework Parser",
|
||||
"failed to collect preference classes");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,40 +601,51 @@ public final class FrameworkResourceParser {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL to the local documentation.
|
||||
* Can return null if no documentation is found in the current SDK.
|
||||
*
|
||||
* @param osDocsPath Path to the documentation folder in the current SDK.
|
||||
* The folder may not actually exist.
|
||||
* @return A file:// URL on the local documentation folder if it exists or null.
|
||||
* Loads the layout bridge from the dynamically loaded layoutlib.jar
|
||||
*/
|
||||
private String getDocumentationBaseUrl(String osDocsPath) {
|
||||
File f = new File(osDocsPath);
|
||||
private LayoutBridge loadLayoutBridge() {
|
||||
LayoutBridge layoutBridge = new LayoutBridge();
|
||||
|
||||
if (f.isDirectory()) {
|
||||
try {
|
||||
// Note: to create a file:// URL, one would typically use something like
|
||||
// f.toURI().toURL().toString(). However this generates a broken path on
|
||||
// Windows, namely "C:\\foo" is converted to "file:/C:/foo" instead of
|
||||
// "file:///C:/foo" (i.e. there should be 3 / after "file:"). So we'll
|
||||
// do the correct thing manually.
|
||||
try {
|
||||
// get the URL for the file.
|
||||
File f = new File(mAndroidTarget.getPath(IAndroidTarget.LAYOUT_LIB));
|
||||
if (f.isFile() == false) {
|
||||
AdtPlugin.log(IStatus.ERROR, "layoutlib.jar is missing!"); //$NON-NLS-1$
|
||||
} else {
|
||||
URL url = f.toURL();
|
||||
|
||||
String path = f.getAbsolutePath();
|
||||
if (File.separatorChar != '/') {
|
||||
path = path.replace(File.separatorChar, '/');
|
||||
// create a class loader. Because this jar reference interfaces
|
||||
// that are in the editors plugin, it's important to provide
|
||||
// a parent class loader.
|
||||
layoutBridge.classLoader = new URLClassLoader(new URL[] { url },
|
||||
this.getClass().getClassLoader());
|
||||
|
||||
// load the class
|
||||
Class<?> clazz = layoutBridge.classLoader.loadClass(AndroidConstants.CLASS_BRIDGE);
|
||||
if (clazz != null) {
|
||||
// instantiate an object of the class.
|
||||
Constructor<?> constructor = clazz.getConstructor();
|
||||
if (constructor != null) {
|
||||
Object bridge = constructor.newInstance();
|
||||
if (bridge instanceof ILayoutBridge) {
|
||||
layoutBridge.bridge = (ILayoutBridge)bridge;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For some reason the URL class doesn't add the mandatory "//" after
|
||||
// the "file:" protocol name, so it has to be hacked into the path.
|
||||
URL url = new URL("file", null, "//" + path); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
String result = url.toString();
|
||||
return result;
|
||||
} catch (MalformedURLException e) {
|
||||
// ignore malformed URLs
|
||||
if (layoutBridge.bridge == null) {
|
||||
layoutBridge.status = LoadStatus.FAILED;
|
||||
AdtPlugin.log(IStatus.ERROR, "Failed to load " + AndroidConstants.CLASS_BRIDGE); //$NON-NLS-1$
|
||||
} else {
|
||||
layoutBridge.status = LoadStatus.LOADED;
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
layoutBridge.status = LoadStatus.FAILED;
|
||||
// log the error.
|
||||
AdtPlugin.log(t, "Failed to load the LayoutLib");
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
return layoutBridge;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.resources;
|
||||
package com.android.ide.eclipse.adt.sdk;
|
||||
|
||||
import com.android.ide.eclipse.common.resources.IResourceRepository;
|
||||
import com.android.ide.eclipse.common.resources.ResourceItem;
|
||||
@@ -26,9 +26,9 @@ import java.util.Set;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link IResourceRepository} interface to hold the system resource Ids
|
||||
* parsed by {@link FrameworkResourceParser}.
|
||||
* parsed by {@link AndroidTargetParser}.
|
||||
*/
|
||||
public final class FrameworkResourceRepository implements IResourceRepository {
|
||||
final class FrameworkResourceRepository implements IResourceRepository {
|
||||
|
||||
private Map<ResourceType, List<ResourceItem>> mResourcesMap;
|
||||
|
||||
@@ -14,9 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.resources;
|
||||
|
||||
import com.android.ide.eclipse.adt.resources.LayoutParamsParser.IClass;
|
||||
package com.android.ide.eclipse.adt.sdk;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
@@ -28,7 +26,25 @@ import javax.management.InvalidAttributeValueException;
|
||||
* Classes which implements this interface provide methods to access framework resource
|
||||
* data loaded from the SDK.
|
||||
*/
|
||||
public interface IAndroidLoader {
|
||||
public interface IAndroidClassLoader {
|
||||
|
||||
/**
|
||||
* Classes which implement this interface provide methods to describe a class.
|
||||
*/
|
||||
public interface IClassDescriptor {
|
||||
|
||||
String getCanonicalName();
|
||||
|
||||
IClassDescriptor getSuperclass();
|
||||
|
||||
String getSimpleName();
|
||||
|
||||
IClassDescriptor getEnclosingClass();
|
||||
|
||||
IClassDescriptor[] getDeclaredClasses();
|
||||
|
||||
boolean isInstantiable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and loads all classes that derive from a given set of super classes.
|
||||
@@ -43,7 +59,7 @@ public interface IAndroidLoader {
|
||||
* @throws InvalidAttributeValueException
|
||||
* @throws ClassFormatError
|
||||
*/
|
||||
public HashMap<String, ArrayList<IClass>> findClassesDerivingFrom(
|
||||
public HashMap<String, ArrayList<IClassDescriptor>> findClassesDerivingFrom(
|
||||
String rootPackage, String[] superClasses)
|
||||
throws IOException, InvalidAttributeValueException, ClassFormatError;
|
||||
|
||||
@@ -52,7 +68,7 @@ public interface IAndroidLoader {
|
||||
* @param className the fully-qualified name of the class to return.
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public IClass getClass(String className) throws ClassNotFoundException;
|
||||
public IClassDescriptor getClass(String className) throws ClassNotFoundException;
|
||||
|
||||
/**
|
||||
* Returns a string indicating the source of the classes, typically for debugging
|
||||
@@ -14,10 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.resources;
|
||||
package com.android.ide.eclipse.adt.sdk;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.sdk.IAndroidClassLoader.IClassDescriptor;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.common.CommonPlugin;
|
||||
import com.android.ide.eclipse.common.resources.AttrsXmlParser;
|
||||
import com.android.ide.eclipse.common.resources.ViewClassInfo;
|
||||
import com.android.ide.eclipse.common.resources.ViewClassInfo.LayoutParamsInfo;
|
||||
@@ -52,23 +53,10 @@ import javax.management.InvalidAttributeValueException;
|
||||
public class LayoutParamsParser {
|
||||
|
||||
/**
|
||||
* Classes which implement this interface provide methods to describe a class.
|
||||
* Class extending {@link ViewClassInfo} by adding the notion of instantiability.
|
||||
* {@link LayoutParamsParser#getViews()} and {@link LayoutParamsParser#getGroups()} should
|
||||
* only return classes that can be instantiated.
|
||||
*/
|
||||
public interface IClass {
|
||||
|
||||
public String getCanonicalName();
|
||||
|
||||
public IClass getSuperclass();
|
||||
|
||||
public String getSimpleName();
|
||||
|
||||
public IClass getEnclosingClass();
|
||||
|
||||
public IClass[] getDeclaredClasses();
|
||||
|
||||
public boolean isInstantiable();
|
||||
}
|
||||
|
||||
final static class ExtViewClassInfo extends ViewClassInfo {
|
||||
|
||||
private boolean mIsInstantiable;
|
||||
@@ -87,16 +75,16 @@ public class LayoutParamsParser {
|
||||
/* Note: protected members/methods are overridden in unit tests */
|
||||
|
||||
/** Reference to android.view.View */
|
||||
protected IClass mTopViewClass;
|
||||
protected IClassDescriptor mTopViewClass;
|
||||
/** Reference to android.view.ViewGroup */
|
||||
protected IClass mTopGroupClass;
|
||||
protected IClassDescriptor mTopGroupClass;
|
||||
/** Reference to android.view.ViewGroup$LayoutParams */
|
||||
protected IClass mTopLayoutParamsClass;
|
||||
protected IClassDescriptor mTopLayoutParamsClass;
|
||||
|
||||
/** Input list of all classes deriving from android.view.View */
|
||||
protected ArrayList<IClass> mViewList;
|
||||
protected ArrayList<IClassDescriptor> mViewList;
|
||||
/** Input list of all classes deriving from android.view.ViewGroup */
|
||||
protected ArrayList<IClass> mGroupList;
|
||||
protected ArrayList<IClassDescriptor> mGroupList;
|
||||
|
||||
/** Output map of FQCN => info on View classes */
|
||||
protected TreeMap<String, ExtViewClassInfo> mViewMap;
|
||||
@@ -109,14 +97,14 @@ public class LayoutParamsParser {
|
||||
protected AttrsXmlParser mAttrsXmlParser;
|
||||
|
||||
/** The android.jar class loader */
|
||||
protected IAndroidLoader mClassLoader;
|
||||
protected IAndroidClassLoader mClassLoader;
|
||||
|
||||
/**
|
||||
* Instantiate a new LayoutParamsParser.
|
||||
* @param classLoader The android.jar class loader
|
||||
* @param attrsXmlParser The parser of the attrs.xml file
|
||||
*/
|
||||
public LayoutParamsParser(IAndroidLoader classLoader,
|
||||
public LayoutParamsParser(IAndroidClassLoader classLoader,
|
||||
AttrsXmlParser attrsXmlParser) {
|
||||
mClassLoader = classLoader;
|
||||
mAttrsXmlParser = attrsXmlParser;
|
||||
@@ -135,8 +123,8 @@ public class LayoutParamsParser {
|
||||
/**
|
||||
* TODO: doc here.
|
||||
* <p/>
|
||||
* Note: on output we should have NO dependency on IClass, otherwise we wouldn't be able
|
||||
* to unload the class loader later.
|
||||
* Note: on output we should have NO dependency on {@link IClassDescriptor},
|
||||
* otherwise we wouldn't be able to unload the class loader later.
|
||||
* <p/>
|
||||
* Note on Vocabulary: FQCN=Fully Qualified Class Name (e.g. "my.package.class$innerClass")
|
||||
* @param monitor A progress monitor. Can be null. Caller is responsible for calling done.
|
||||
@@ -168,8 +156,8 @@ public class LayoutParamsParser {
|
||||
if (paramsClassName != null) {
|
||||
superClasses[2] = paramsClassName;
|
||||
}
|
||||
HashMap<String, ArrayList<IClass>> found = mClassLoader.findClassesDerivingFrom(
|
||||
"android.", superClasses);
|
||||
HashMap<String, ArrayList<IClassDescriptor>> found =
|
||||
mClassLoader.findClassesDerivingFrom("android.", superClasses);
|
||||
mTopViewClass = mClassLoader.getClass(rootClassName);
|
||||
mTopGroupClass = mClassLoader.getClass(groupClassName);
|
||||
if (paramsClassName != null) {
|
||||
@@ -196,26 +184,26 @@ public class LayoutParamsParser {
|
||||
|
||||
progress.setWorkRemaining(mGroupList.size() + mViewList.size());
|
||||
|
||||
for (IClass groupChild : mGroupList) {
|
||||
for (IClassDescriptor groupChild : mGroupList) {
|
||||
addGroup(groupChild);
|
||||
progress.worked(1);
|
||||
}
|
||||
|
||||
for (IClass viewChild : mViewList) {
|
||||
for (IClassDescriptor viewChild : mViewList) {
|
||||
if (viewChild != mTopGroupClass) {
|
||||
addView(viewChild);
|
||||
}
|
||||
progress.worked(1);
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
CommonPlugin.log(e, "Problem loading class %1$s or %2$s", //$NON-NLS-1$
|
||||
AdtPlugin.log(e, "Problem loading class %1$s or %2$s", //$NON-NLS-1$
|
||||
rootClassName, groupClassName);
|
||||
} catch (InvalidAttributeValueException e) {
|
||||
CommonPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
|
||||
AdtPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
|
||||
} catch (ClassFormatError e) {
|
||||
CommonPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
|
||||
AdtPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
|
||||
} catch (IOException e) {
|
||||
CommonPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
|
||||
AdtPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +211,7 @@ public class LayoutParamsParser {
|
||||
* Parses a View class and adds a ExtViewClassInfo for it in mViewMap.
|
||||
* It calls itself recursively to handle super classes which are also Views.
|
||||
*/
|
||||
private ExtViewClassInfo addView(IClass viewClass) {
|
||||
private ExtViewClassInfo addView(IClassDescriptor viewClass) {
|
||||
String fqcn = viewClass.getCanonicalName();
|
||||
if (mViewMap.containsKey(fqcn)) {
|
||||
return mViewMap.get(fqcn);
|
||||
@@ -238,7 +226,7 @@ public class LayoutParamsParser {
|
||||
// All view classes derive from mTopViewClass by design.
|
||||
// Do not lookup the super class for mTopViewClass itself.
|
||||
if (viewClass.equals(mTopViewClass) == false) {
|
||||
IClass superClass = viewClass.getSuperclass();
|
||||
IClassDescriptor superClass = viewClass.getSuperclass();
|
||||
ExtViewClassInfo superClassInfo = addView(superClass);
|
||||
info.setSuperClass(superClassInfo);
|
||||
}
|
||||
@@ -251,7 +239,7 @@ public class LayoutParamsParser {
|
||||
* Parses a ViewGroup class and adds a ExtViewClassInfo for it in mGroupMap.
|
||||
* It calls itself recursively to handle super classes which are also ViewGroups.
|
||||
*/
|
||||
private ExtViewClassInfo addGroup(IClass groupClass) {
|
||||
private ExtViewClassInfo addGroup(IClassDescriptor groupClass) {
|
||||
String fqcn = groupClass.getCanonicalName();
|
||||
if (mGroupMap.containsKey(fqcn)) {
|
||||
return mGroupMap.get(fqcn);
|
||||
@@ -265,7 +253,7 @@ public class LayoutParamsParser {
|
||||
// android.view.View (i.e. mTopViewClass here). So the only group that can have View as
|
||||
// its super class is the ViewGroup base class and we don't try to resolve it since groups
|
||||
// are loaded before views.
|
||||
IClass superClass = groupClass.getSuperclass();
|
||||
IClassDescriptor superClass = groupClass.getSuperclass();
|
||||
|
||||
// Assertion: at this point, we should have
|
||||
// superClass != mTopViewClass || fqcn.equals(AndroidConstants.CLASS_VIEWGROUP);
|
||||
@@ -291,15 +279,15 @@ public class LayoutParamsParser {
|
||||
*
|
||||
* @return The {@link LayoutParamsInfo} for the ViewGroup class or null.
|
||||
*/
|
||||
private LayoutParamsInfo addLayoutParams(IClass groupClass) {
|
||||
private LayoutParamsInfo addLayoutParams(IClassDescriptor groupClass) {
|
||||
|
||||
// Is there a LayoutParams in this group class?
|
||||
IClass layoutParamsClass = findLayoutParams(groupClass);
|
||||
IClassDescriptor layoutParamsClass = findLayoutParams(groupClass);
|
||||
|
||||
// if there's no layout data in the group class, link to the one from the
|
||||
// super class.
|
||||
if (layoutParamsClass == null) {
|
||||
for (IClass superClass = groupClass.getSuperclass();
|
||||
for (IClassDescriptor superClass = groupClass.getSuperclass();
|
||||
layoutParamsClass == null &&
|
||||
superClass != null &&
|
||||
superClass.equals(mTopViewClass) == false;
|
||||
@@ -319,7 +307,7 @@ public class LayoutParamsParser {
|
||||
* Parses a LayoutParams class and returns a LayoutParamsInfo object for it.
|
||||
* It calls itself recursively to handle the super class of the LayoutParams.
|
||||
*/
|
||||
private LayoutParamsInfo getLayoutParamsInfo(IClass layoutParamsClass) {
|
||||
private LayoutParamsInfo getLayoutParamsInfo(IClassDescriptor layoutParamsClass) {
|
||||
String fqcn = layoutParamsClass.getCanonicalName();
|
||||
LayoutParamsInfo layoutParamsInfo = mLayoutParamsMap.get(fqcn);
|
||||
|
||||
@@ -330,7 +318,7 @@ public class LayoutParamsParser {
|
||||
// Find the link on the LayoutParams super class
|
||||
LayoutParamsInfo superClassInfo = null;
|
||||
if (layoutParamsClass.equals(mTopLayoutParamsClass) == false) {
|
||||
IClass superClass = layoutParamsClass.getSuperclass();
|
||||
IClassDescriptor superClass = layoutParamsClass.getSuperclass();
|
||||
superClassInfo = getLayoutParamsInfo(superClass);
|
||||
}
|
||||
|
||||
@@ -355,9 +343,9 @@ public class LayoutParamsParser {
|
||||
* @param groupClass The ViewGroup derived class
|
||||
* @return The Class of the inner LayoutParams or null if none is declared.
|
||||
*/
|
||||
private IClass findLayoutParams(IClass groupClass) {
|
||||
IClass[] innerClasses = groupClass.getDeclaredClasses();
|
||||
for (IClass innerClass : innerClasses) {
|
||||
private IClassDescriptor findLayoutParams(IClassDescriptor groupClass) {
|
||||
IClassDescriptor[] innerClasses = groupClass.getDeclaredClasses();
|
||||
for (IClassDescriptor innerClass : innerClasses) {
|
||||
if (innerClass.getSimpleName().equals(AndroidConstants.CLASS_LAYOUTPARAMS)) {
|
||||
return innerClass;
|
||||
}
|
||||
@@ -365,12 +353,16 @@ public class LayoutParamsParser {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes and return a list of ViewClassInfo from a map by filtering out the class that
|
||||
* cannot be instantiated.
|
||||
*/
|
||||
private List<ViewClassInfo> getInstantiables(SortedMap<String, ExtViewClassInfo> map) {
|
||||
Collection<ExtViewClassInfo> values = map.values();
|
||||
ArrayList<ViewClassInfo> list = new ArrayList<ViewClassInfo>();
|
||||
|
||||
for (ExtViewClassInfo info : values) {
|
||||
if (info.mIsInstantiable) {
|
||||
if (info.isInstantiable()) {
|
||||
list.add(info);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.sdk;
|
||||
|
||||
/**
|
||||
* Enum for loading status of various SDK parts.
|
||||
*/
|
||||
public enum LoadStatus {
|
||||
LOADING, LOADED, FAILED;
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.sdk;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.project.internal.AndroidClasspathContainerInitializer;
|
||||
import com.android.sdklib.IAndroidTarget;
|
||||
import com.android.sdklib.ISdkLog;
|
||||
import com.android.sdklib.SdkConstants;
|
||||
import com.android.sdklib.SdkManager;
|
||||
import com.android.sdklib.project.ProjectProperties;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IProjectDescription;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.NullProgressMonitor;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jdt.core.JavaCore;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Central point to load, manipulate and deal with the Android SDK. Only one SDK can be used
|
||||
* at the same time.
|
||||
*
|
||||
* To start using an SDK, call {@link #loadSdk(String)} which returns the instance of
|
||||
* the Sdk object.
|
||||
*
|
||||
* To get the list of platforms present in the SDK, call {@link #getPlatforms()}.
|
||||
* To get the list of add-ons present in the SDK, call {@link #getAddons()}.
|
||||
*
|
||||
*/
|
||||
public class Sdk {
|
||||
private final static String PROPERTY_PROJECT_TARGET = "androidTarget"; //$NON-NLS-1$
|
||||
|
||||
private static Sdk sCurrentSdk = null;
|
||||
|
||||
private final SdkManager mManager;
|
||||
private final HashMap<IProject, IAndroidTarget> mProjectMap =
|
||||
new HashMap<IProject, IAndroidTarget>();
|
||||
private final HashMap<IAndroidTarget, AndroidTargetData> mTargetMap =
|
||||
new HashMap<IAndroidTarget, AndroidTargetData>();
|
||||
private final String mDocBaseUrl;
|
||||
|
||||
/**
|
||||
* Loads an SDK and returns an {@link Sdk} object if success.
|
||||
* @param sdkLocation the OS path to the SDK.
|
||||
*/
|
||||
public static Sdk loadSdk(String sdkLocation) {
|
||||
if (sCurrentSdk != null) {
|
||||
// manual unload?
|
||||
sCurrentSdk = null;
|
||||
}
|
||||
|
||||
final ArrayList<String> logMessages = new ArrayList<String>();
|
||||
ISdkLog log = new ISdkLog() {
|
||||
public void error(String errorFormat, Object... arg) {
|
||||
logMessages.add(String.format(errorFormat, arg));
|
||||
}
|
||||
public void warning(String warningFormat, Object... arg) {
|
||||
logMessages.add(String.format(warningFormat, arg));
|
||||
}
|
||||
};
|
||||
|
||||
// get an SdkManager object for the location
|
||||
SdkManager manager = SdkManager.createManager(sdkLocation, log);
|
||||
if (manager != null) {
|
||||
sCurrentSdk = new Sdk(manager);
|
||||
return sCurrentSdk;
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder("Error Loading the SDK:\n");
|
||||
for (String msg : logMessages) {
|
||||
sb.append('\n');
|
||||
sb.append(msg);
|
||||
}
|
||||
AdtPlugin.displayError("Android SDK", sb.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current {@link Sdk} object.
|
||||
*/
|
||||
public static Sdk getCurrent() {
|
||||
return sCurrentSdk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL to the local documentation.
|
||||
* Can return null if no documentation is found in the current SDK.
|
||||
*
|
||||
* @return A file:// URL on the local documentation folder if it exists or null.
|
||||
*/
|
||||
public String getDocumentationBaseUrl() {
|
||||
return mDocBaseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of targets that are available in the SDK.
|
||||
*/
|
||||
public IAndroidTarget[] getTargets() {
|
||||
return mManager.getTargets();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a target from a hash that was generated by {@link IAndroidTarget#hashString()}.
|
||||
* @param hash the hash
|
||||
*/
|
||||
public IAndroidTarget getTargetFromHashString(String hash) {
|
||||
return mManager.getTargetFromHashString(hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates an {@link IProject} and an {@link IAndroidTarget}.
|
||||
*/
|
||||
public void setProject(IProject project, IAndroidTarget target) {
|
||||
synchronized (mProjectMap) {
|
||||
// look for the current target of the project
|
||||
IAndroidTarget previousTarget = mProjectMap.get(project);
|
||||
|
||||
if (target != previousTarget) {
|
||||
// save the target hash string in the project persistent property
|
||||
setProjectTargetHashString(project, target.hashString());
|
||||
|
||||
// put it in a local map for easy access.
|
||||
mProjectMap.put(project, target);
|
||||
|
||||
// recompile the project if needed.
|
||||
IJavaProject javaProject = JavaCore.create(project);
|
||||
AndroidClasspathContainerInitializer.updateProjects(
|
||||
new IJavaProject[] { javaProject });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link IAndroidTarget} object associated with the given {@link IProject}.
|
||||
*/
|
||||
public IAndroidTarget getTarget(IProject project) {
|
||||
synchronized (mProjectMap) {
|
||||
IAndroidTarget target = mProjectMap.get(project);
|
||||
if (target == null) {
|
||||
// get the value from the project persistent property.
|
||||
String targetHashString = getProjectTargetHashString(project);
|
||||
|
||||
if (targetHashString != null) {
|
||||
target = mManager.getTargetFromHashString(targetHashString);
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hash string uniquely identifying the target of a project. This methods reads
|
||||
* the string from the project persistent preferences/properties.
|
||||
* <p/>The string is equivalent to the return of {@link IAndroidTarget#hashString()}.
|
||||
* @param project The project for which to return the target hash string.
|
||||
* @return the hash string or null if the project does not have a target set.
|
||||
*/
|
||||
public static String getProjectTargetHashString(IProject project) {
|
||||
// load the default.properties from the project folder.
|
||||
ProjectProperties properties = ProjectProperties.load(project.getLocation().toOSString());
|
||||
if (properties == null) {
|
||||
AdtPlugin.log(IStatus.ERROR, "Failed to load properties file for project '%s'",
|
||||
project.getName());
|
||||
return null;
|
||||
}
|
||||
|
||||
return properties.getProperty(ProjectProperties.PROPERTY_TARGET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a target hash string in a project's persistent preferences/property storage.
|
||||
* @param project The project in which to save the hash string.
|
||||
* @param targetHashString The target hash string to save. This must be the result from
|
||||
* {@link IAndroidTarget#hashString()}.
|
||||
*/
|
||||
public static void setProjectTargetHashString(IProject project, String targetHashString) {
|
||||
// because we don't want to erase other properties from default.properties, we first load
|
||||
// them
|
||||
ProjectProperties properties = ProjectProperties.load(project.getLocation().toOSString());
|
||||
if (properties == null) {
|
||||
// doesn't exist yet? we create it.
|
||||
properties = ProjectProperties.create(project.getLocation().toOSString());
|
||||
}
|
||||
|
||||
// add/change the target hash string.
|
||||
properties.setProperty(ProjectProperties.PROPERTY_TARGET, targetHashString);
|
||||
|
||||
// and rewrite the file.
|
||||
try {
|
||||
properties.save();
|
||||
} catch (IOException e) {
|
||||
AdtPlugin.log(e, "Failed to save default.properties for project '%s'",
|
||||
project.getName());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Return the {@link PlatformData} for a given {@link IAndroidTarget}.
|
||||
*/
|
||||
public AndroidTargetData getTargetData(IAndroidTarget target) {
|
||||
synchronized (mTargetMap) {
|
||||
return mTargetMap.get(target);
|
||||
}
|
||||
}
|
||||
|
||||
private Sdk(SdkManager manager) {
|
||||
mManager = manager;
|
||||
|
||||
// pre-compute some paths
|
||||
mDocBaseUrl = getDocumentationBaseUrl(mManager.getLocation() +
|
||||
SdkConstants.OS_SDK_DOCS_FOLDER);
|
||||
}
|
||||
|
||||
void setTargetData(IAndroidTarget target, AndroidTargetData data) {
|
||||
synchronized (mTargetMap) {
|
||||
mTargetMap.put(target, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL to the local documentation.
|
||||
* Can return null if no documentation is found in the current SDK.
|
||||
*
|
||||
* @param osDocsPath Path to the documentation folder in the current SDK.
|
||||
* The folder may not actually exist.
|
||||
* @return A file:// URL on the local documentation folder if it exists or null.
|
||||
*/
|
||||
private String getDocumentationBaseUrl(String osDocsPath) {
|
||||
File f = new File(osDocsPath);
|
||||
|
||||
if (f.isDirectory()) {
|
||||
try {
|
||||
// Note: to create a file:// URL, one would typically use something like
|
||||
// f.toURI().toURL().toString(). However this generates a broken path on
|
||||
// Windows, namely "C:\\foo" is converted to "file:/C:/foo" instead of
|
||||
// "file:///C:/foo" (i.e. there should be 3 / after "file:"). So we'll
|
||||
// do the correct thing manually.
|
||||
|
||||
String path = f.getAbsolutePath();
|
||||
if (File.separatorChar != '/') {
|
||||
path = path.replace(File.separatorChar, '/');
|
||||
}
|
||||
|
||||
// For some reason the URL class doesn't add the mandatory "//" after
|
||||
// the "file:" protocol name, so it has to be hacked into the path.
|
||||
URL url = new URL("file", null, "//" + path); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
String result = url.toString();
|
||||
return result;
|
||||
} catch (MalformedURLException e) {
|
||||
// ignore malformed URLs
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.resources;
|
||||
package com.android.ide.eclipse.adt.sdk;
|
||||
|
||||
import com.android.ide.eclipse.adt.resources.LayoutParamsParser.IClass;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
@@ -42,18 +41,19 @@ import javax.management.InvalidAttributeValueException;
|
||||
* where code is a single letter (W for widget, L for layout, P for layout params), and class names
|
||||
* are the fully qualified name of the classes.
|
||||
*/
|
||||
public final class WidgetListLoader implements IAndroidLoader {
|
||||
public final class WidgetClassLoader implements IAndroidClassLoader {
|
||||
|
||||
/**
|
||||
* Basic class containing the class descriptions found in the text file.
|
||||
*/
|
||||
private final static class ClassDescriptor implements IClass {
|
||||
private final static class ClassDescriptor implements IClassDescriptor {
|
||||
|
||||
private String mName;
|
||||
private String mSimpleName;
|
||||
private ClassDescriptor mSuperClass;
|
||||
private ClassDescriptor mEnclosingClass;
|
||||
private final ArrayList<IClass> mDeclaredClasses = new ArrayList<IClass>();
|
||||
private final ArrayList<IClassDescriptor> mDeclaredClasses =
|
||||
new ArrayList<IClassDescriptor>();
|
||||
private boolean mIsInstantiable = false;
|
||||
|
||||
ClassDescriptor(String fqcn) {
|
||||
@@ -69,15 +69,15 @@ public final class WidgetListLoader implements IAndroidLoader {
|
||||
return mSimpleName;
|
||||
}
|
||||
|
||||
public IClass[] getDeclaredClasses() {
|
||||
return mDeclaredClasses.toArray(new IClass[mDeclaredClasses.size()]);
|
||||
public IClassDescriptor[] getDeclaredClasses() {
|
||||
return mDeclaredClasses.toArray(new IClassDescriptor[mDeclaredClasses.size()]);
|
||||
}
|
||||
|
||||
private void addDeclaredClass(ClassDescriptor declaredClass) {
|
||||
mDeclaredClasses.add(declaredClass);
|
||||
}
|
||||
|
||||
public IClass getEnclosingClass() {
|
||||
public IClassDescriptor getEnclosingClass() {
|
||||
return mEnclosingClass;
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ public final class WidgetListLoader implements IAndroidLoader {
|
||||
mName = enclosingClass.mName + "$" + mName.substring(enclosingClass.mName.length() + 1);
|
||||
}
|
||||
|
||||
public IClass getSuperclass() {
|
||||
public IClassDescriptor getSuperclass() {
|
||||
return mSuperClass;
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ public final class WidgetListLoader implements IAndroidLoader {
|
||||
* @param osFilePath the OS path of the file to load.
|
||||
* @throws FileNotFoundException if the file is not found.
|
||||
*/
|
||||
WidgetListLoader(String osFilePath) throws FileNotFoundException {
|
||||
WidgetClassLoader(String osFilePath) throws FileNotFoundException {
|
||||
mOsFilePath = osFilePath;
|
||||
mReader = new BufferedReader(new FileReader(osFilePath));
|
||||
}
|
||||
@@ -301,20 +301,21 @@ public final class WidgetListLoader implements IAndroidLoader {
|
||||
* @throws InvalidAttributeValueException
|
||||
* @throws ClassFormatError
|
||||
*/
|
||||
public HashMap<String, ArrayList<IClass>> findClassesDerivingFrom(String rootPackage,
|
||||
public HashMap<String, ArrayList<IClassDescriptor>> findClassesDerivingFrom(String rootPackage,
|
||||
String[] superClasses) throws IOException, InvalidAttributeValueException,
|
||||
ClassFormatError {
|
||||
HashMap<String, ArrayList<IClass>> map = new HashMap<String, ArrayList<IClass>>();
|
||||
HashMap<String, ArrayList<IClassDescriptor>> map =
|
||||
new HashMap<String, ArrayList<IClassDescriptor>>();
|
||||
|
||||
ArrayList<IClass> list = new ArrayList<IClass>();
|
||||
ArrayList<IClassDescriptor> list = new ArrayList<IClassDescriptor>();
|
||||
list.addAll(mWidgetMap.values());
|
||||
map.put(AndroidConstants.CLASS_VIEW, list);
|
||||
|
||||
list = new ArrayList<IClass>();
|
||||
list = new ArrayList<IClassDescriptor>();
|
||||
list.addAll(mLayoutMap.values());
|
||||
map.put(AndroidConstants.CLASS_VIEWGROUP, list);
|
||||
|
||||
list = new ArrayList<IClass>();
|
||||
list = new ArrayList<IClassDescriptor>();
|
||||
list.addAll(mLayoutParamsMap.values());
|
||||
map.put(AndroidConstants.CLASS_VIEWGROUP_LAYOUTPARAMS, list);
|
||||
|
||||
@@ -326,7 +327,7 @@ public final class WidgetListLoader implements IAndroidLoader {
|
||||
* @param className the fully-qualified name of the class to return.
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public IClass getClass(String className) throws ClassNotFoundException {
|
||||
public IClassDescriptor getClass(String className) throws ClassNotFoundException {
|
||||
return mMap.get(className);
|
||||
}
|
||||
|
||||
@@ -20,11 +20,13 @@
|
||||
* org.eclipse.jdt.internal.ui.wizards.JavaProjectWizardFirstPage
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.project.internal;
|
||||
package com.android.ide.eclipse.adt.wizards.newproject;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.sdk.Sdk;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.common.project.AndroidManifestHelper;
|
||||
import com.android.sdklib.IAndroidTarget;
|
||||
import com.android.sdkuilib.SdkTargetSelector;
|
||||
|
||||
import org.eclipse.core.filesystem.URIUtil;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
@@ -120,6 +122,7 @@ public class NewProjectCreationPage extends WizardPage {
|
||||
private boolean mInternalActivityNameUpdate;
|
||||
protected boolean mProjectNameModifiedByUser;
|
||||
protected boolean mApplicationNameModifiedByUser;
|
||||
private SdkTargetSelector mSdkTargetSelector;
|
||||
|
||||
|
||||
/**
|
||||
@@ -133,7 +136,9 @@ public class NewProjectCreationPage extends WizardPage {
|
||||
if (sCustomLocationOsPath == null ||
|
||||
sCustomLocationOsPath.length() == 0 ||
|
||||
!new File(sCustomLocationOsPath).isDirectory()) {
|
||||
sCustomLocationOsPath = AdtPlugin.getOsSdkSamplesFolder();
|
||||
// FIXME location of samples is pretty much impossible here.
|
||||
//sCustomLocationOsPath = AdtPlugin.getOsSdkSamplesFolder();
|
||||
sCustomLocationOsPath = File.listRoots()[0].getAbsolutePath();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,6 +205,11 @@ public class NewProjectCreationPage extends WizardPage {
|
||||
return mSourceFolder;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the current sdk target or null if none has been selected yet. */
|
||||
public IAndroidTarget getSdkTarget() {
|
||||
return mSdkTargetSelector == null ? null : mSdkTargetSelector.getFirstSelected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides @DialogPage.setVisible(boolean) to put the focus in the project name when
|
||||
@@ -232,15 +242,19 @@ public class NewProjectCreationPage extends WizardPage {
|
||||
|
||||
createProjectNameGroup(composite);
|
||||
createLocationGroup(composite);
|
||||
createTargetGroup(composite);
|
||||
createPropertiesGroup(composite);
|
||||
|
||||
// Update state the first time
|
||||
enableLocationWidgets();
|
||||
setPageComplete(validatePage());
|
||||
|
||||
// Show description the first time
|
||||
setErrorMessage(null);
|
||||
setMessage(null);
|
||||
setControl(composite);
|
||||
|
||||
// Validate. This will complain about the first empty field.
|
||||
setPageComplete(validatePage());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -356,6 +370,33 @@ public class NewProjectCreationPage extends WizardPage {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the target group.
|
||||
* It only contains an SdkTargetSelector.
|
||||
*/
|
||||
private void createTargetGroup(Composite parent) {
|
||||
Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
|
||||
// Layout has 1 column
|
||||
group.setLayout(new GridLayout());
|
||||
group.setLayoutData(new GridData(GridData.FILL_BOTH));
|
||||
group.setFont(parent.getFont());
|
||||
group.setText("Target");
|
||||
|
||||
// get the targets from the sdk
|
||||
IAndroidTarget[] targets = null;
|
||||
if (Sdk.getCurrent() != null) {
|
||||
targets = Sdk.getCurrent().getTargets();
|
||||
}
|
||||
|
||||
mSdkTargetSelector = new SdkTargetSelector(group, targets, false /*multi-selection*/);
|
||||
mSdkTargetSelector.setSelectionListener(new SelectionAdapter() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent e) {
|
||||
setPageComplete(validatePage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a directory browser and update the location path field with the selected path
|
||||
*/
|
||||
@@ -754,6 +795,9 @@ public class NewProjectCreationPage extends WizardPage {
|
||||
if ((status & MSG_ERROR) == 0) {
|
||||
status |= validateLocationPath(workspace);
|
||||
}
|
||||
if ((status & MSG_ERROR) == 0) {
|
||||
status |= validateSdkTarget();
|
||||
}
|
||||
if ((status & MSG_ERROR) == 0) {
|
||||
status |= validatePackageField();
|
||||
}
|
||||
@@ -893,6 +937,18 @@ public class NewProjectCreationPage extends WizardPage {
|
||||
return MSG_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the sdk target choice.
|
||||
*
|
||||
* @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
|
||||
*/
|
||||
private int validateSdkTarget() {
|
||||
if (getSdkTarget() == null) {
|
||||
return setStatus("An SDK Target must be specified.", MSG_ERROR);
|
||||
}
|
||||
return MSG_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the activity name field.
|
||||
*
|
||||
@@ -14,12 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.project.internal;
|
||||
package com.android.ide.eclipse.adt.wizards.newproject;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.project.AndroidNature;
|
||||
import com.android.ide.eclipse.adt.project.ProjectHelper;
|
||||
import com.android.ide.eclipse.adt.sdk.Sdk;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.sdklib.IAndroidTarget;
|
||||
|
||||
import org.eclipse.core.resources.IContainer;
|
||||
import org.eclipse.core.resources.IFile;
|
||||
@@ -60,6 +62,7 @@ import java.net.MalformedURLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* A "New Android Project" Wizard.
|
||||
@@ -81,6 +84,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
|
||||
private static final String PARAM_STRING_CONTENT = "STRING_CONTENT"; //$NON-NLS-1$
|
||||
private static final String PARAM_IS_NEW_PROJECT = "IS_NEW_PROJECT"; //$NON-NLS-1$
|
||||
private static final String PARAM_SRC_FOLDER = "SRC_FOLDER"; //$NON-NLS-1$
|
||||
private static final String PARAM_SDK_TARGET = "SDK_TARGET"; //$NON-NLS-1$
|
||||
|
||||
private static final String PH_ACTIVITIES = "ACTIVITIES"; //$NON-NLS-1$
|
||||
private static final String PH_INTENT_FILTERS = "INTENT_FILTERS"; //$NON-NLS-1$
|
||||
@@ -223,13 +227,14 @@ public class NewProjectWizard extends Wizard implements INewWizard {
|
||||
final IProject project = workspace.getRoot().getProject(mMainPage.getProjectName());
|
||||
final IProjectDescription description = workspace.newProjectDescription(project.getName());
|
||||
|
||||
final Map<String, String> parameters = new HashMap<String, String>();
|
||||
final Map<String, Object> parameters = new HashMap<String, Object>();
|
||||
parameters.put(PARAM_PROJECT, mMainPage.getProjectName());
|
||||
parameters.put(PARAM_PACKAGE, mMainPage.getPackageName());
|
||||
parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME);
|
||||
parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder());
|
||||
parameters.put(PARAM_IS_NEW_PROJECT, Boolean.toString(mMainPage.isNewProject()));
|
||||
parameters.put(PARAM_IS_NEW_PROJECT, mMainPage.isNewProject());
|
||||
parameters.put(PARAM_SRC_FOLDER, mMainPage.getSourceFolder());
|
||||
parameters.put(PARAM_SDK_TARGET, mMainPage.getSdkTarget());
|
||||
|
||||
if (mMainPage.isCreateActivity()) {
|
||||
// An activity name can be of the form ".package.Class" or ".Class".
|
||||
@@ -315,7 +320,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
|
||||
* to create or modify the project or if it is canceled by the user.
|
||||
*/
|
||||
private void createProjectAsync(IProject project, IProjectDescription description,
|
||||
IProgressMonitor monitor, Map<String, String> parameters,
|
||||
IProgressMonitor monitor, Map<String, Object> parameters,
|
||||
Map<String, String> stringDictionary)
|
||||
throws InvocationTargetException {
|
||||
monitor.beginTask("Create Android Project", 100);
|
||||
@@ -330,7 +335,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
|
||||
|
||||
// Create folders in the project if they don't already exist
|
||||
addDefaultDirectories(project, AndroidConstants.WS_ROOT, DEFAULT_DIRECTORIES, monitor);
|
||||
String[] sourceFolder = new String[] { parameters.get(PARAM_SRC_FOLDER) };
|
||||
String[] sourceFolder = new String[] { (String) parameters.get(PARAM_SRC_FOLDER) };
|
||||
addDefaultDirectories(project, AndroidConstants.WS_ROOT, sourceFolder, monitor);
|
||||
|
||||
// Create the resource folders in the project if they don't already exist.
|
||||
@@ -340,7 +345,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
|
||||
IJavaProject javaProject = JavaCore.create(project);
|
||||
setupSourceFolder(javaProject, sourceFolder[0], monitor);
|
||||
|
||||
if (Boolean.parseBoolean(parameters.get(PARAM_IS_NEW_PROJECT))) {
|
||||
if (((Boolean) parameters.get(PARAM_IS_NEW_PROJECT)).booleanValue()) {
|
||||
// Create files in the project if they don't already exist
|
||||
addManifest(project, parameters, stringDictionary, monitor);
|
||||
|
||||
@@ -360,6 +365,8 @@ public class NewProjectWizard extends Wizard implements INewWizard {
|
||||
monitor);
|
||||
}
|
||||
|
||||
Sdk.getCurrent().setProject(project, (IAndroidTarget) parameters.get(PARAM_SDK_TARGET));
|
||||
|
||||
// Fix the project to make sure all properties are as expected.
|
||||
// Necessary for existing projects and good for new ones to.
|
||||
ProjectHelper.fixProject(project);
|
||||
@@ -409,7 +416,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
|
||||
* @throws IOException if the method fails to create the files in the
|
||||
* project.
|
||||
*/
|
||||
private void addManifest(IProject project, Map<String, String> parameters,
|
||||
private void addManifest(IProject project, Map<String, Object> parameters,
|
||||
Map<String, String> stringDictionary, IProgressMonitor monitor)
|
||||
throws CoreException, IOException {
|
||||
|
||||
@@ -543,16 +550,16 @@ public class NewProjectWizard extends Wizard implements INewWizard {
|
||||
* project.
|
||||
*/
|
||||
private void addSampleCode(IProject project, String sourceFolder,
|
||||
Map<String, String> parameters, Map<String, String> stringDictionary,
|
||||
Map<String, Object> parameters, Map<String, String> stringDictionary,
|
||||
IProgressMonitor monitor) throws CoreException, IOException {
|
||||
// create the java package directories.
|
||||
IFolder pkgFolder = project.getFolder(sourceFolder);
|
||||
String packageName = parameters.get(PARAM_PACKAGE);
|
||||
String packageName = (String) parameters.get(PARAM_PACKAGE);
|
||||
|
||||
// The PARAM_ACTIVITY key will be absent if no activity should be created,
|
||||
// in which case activityName will be null.
|
||||
String activityName = parameters.get(PARAM_ACTIVITY);
|
||||
Map<String, String> java_activity_parameters = parameters;
|
||||
String activityName = (String) parameters.get(PARAM_ACTIVITY);
|
||||
Map<String, Object> java_activity_parameters = parameters;
|
||||
if (activityName != null) {
|
||||
if (activityName.indexOf('.') >= 0) {
|
||||
// There are package names in the activity name. Transform packageName to add
|
||||
@@ -564,7 +571,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
|
||||
|
||||
// Also update the values used in the JAVA_FILE_TEMPLATE below
|
||||
// (but not the ones from the manifest so don't change the caller's dictionary)
|
||||
java_activity_parameters = new HashMap<String, String>(parameters);
|
||||
java_activity_parameters = new HashMap<String, Object>(parameters);
|
||||
java_activity_parameters.put(PARAM_PACKAGE, packageName);
|
||||
java_activity_parameters.put(PARAM_ACTIVITY, activityName);
|
||||
}
|
||||
@@ -665,7 +672,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
|
||||
* length.
|
||||
*/
|
||||
private void copyFile(String resourceFilename, IFile destFile,
|
||||
Map<String, String> parameters, IProgressMonitor monitor)
|
||||
Map<String, Object> parameters, IProgressMonitor monitor)
|
||||
throws CoreException, IOException {
|
||||
|
||||
// Read existing file.
|
||||
@@ -692,13 +699,14 @@ public class NewProjectWizard extends Wizard implements INewWizard {
|
||||
* Replaces placeholders found in a string with values.
|
||||
*
|
||||
* @param str the string to search for placeholders.
|
||||
* @param parameters a map of <placeholder, Value> to search for in the
|
||||
* string
|
||||
* @param parameters a map of <placeholder, Value> to search for in the string
|
||||
* @return A new String object with the placeholder replaced by the values.
|
||||
*/
|
||||
private String replaceParameters(String str, Map<String, String> parameters) {
|
||||
for (String key : parameters.keySet()) {
|
||||
str = str.replaceAll(key, parameters.get(key));
|
||||
private String replaceParameters(String str, Map<String, Object> parameters) {
|
||||
for (Entry<String, Object> entry : parameters.entrySet()) {
|
||||
if (entry.getValue() instanceof String) {
|
||||
str = str.replaceAll(entry.getKey(), (String) entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.common;
|
||||
|
||||
import com.android.sdklib.SdkConstants;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Constant definition class.<br>
|
||||
* <br>
|
||||
* Most constants have a prefix defining the content.
|
||||
* <ul>
|
||||
* <li><code>WS_</code> Workspace path constant. Those are absolute paths,
|
||||
* from the project root.</li>
|
||||
* <li><code>OS_</code> OS path constant. These paths are different depending on the platform.</li>
|
||||
* <li><code>FN_</code> File name constant.</li>
|
||||
* <li><code>FD_</code> Folder name constant.</li>
|
||||
* <li><code>MARKER_</code> Resource Marker Ids constant.</li>
|
||||
* <li><code>EXT_</code> File extension constant. This does NOT include a dot.</li>
|
||||
* <li><code>DOT_</code> File extension constant. This start with a dot.</li>
|
||||
* <li><code>RE_</code> Regexp constant.</li>
|
||||
* <li><code>NS_</code> Namespace constant.</li>
|
||||
* <li><code>CLASS_</code> Fully qualified class name.</li>
|
||||
* </ul>
|
||||
*
|
||||
*/
|
||||
public class AndroidConstants {
|
||||
/**
|
||||
* The old Editors Plugin ID. It is still used in some places for compatibility.
|
||||
* Please do not use for new features.
|
||||
*/
|
||||
public static final String EDITORS_NAMESPACE = "com.android.ide.eclipse.editors"; // $NON-NLS-1$
|
||||
|
||||
/** Nature of android projects */
|
||||
public final static String NATURE = "com.android.ide.eclipse.adt.AndroidNature"; //$NON-NLS-1$
|
||||
|
||||
public final static int PLATFORM_UNKNOWN = 0;
|
||||
public final static int PLATFORM_LINUX = 1;
|
||||
public final static int PLATFORM_WINDOWS = 2;
|
||||
public final static int PLATFORM_DARWIN = 3;
|
||||
|
||||
/**
|
||||
* Returns current platform, one of {@link #PLATFORM_WINDOWS}, {@link #PLATFORM_DARWIN},
|
||||
* {@link #PLATFORM_LINUX} or {@link #PLATFORM_UNKNOWN}.
|
||||
*/
|
||||
public final static int CURRENT_PLATFORM = currentPlatform();
|
||||
|
||||
/** Separator for workspace path, i.e. "/". */
|
||||
public final static String WS_SEP = "/"; //$NON-NLS-1$
|
||||
/** Separator character for workspace path, i.e. '/'. */
|
||||
public final static char WS_SEP_CHAR = '/';
|
||||
|
||||
/** Extension of the Application package Files, i.e. "apk". */
|
||||
public final static String EXT_ANDROID_PACKAGE = "apk"; //$NON-NLS-1$
|
||||
/** Extension of java files, i.e. "java" */
|
||||
public final static String EXT_JAVA = "java"; //$NON-NLS-1$
|
||||
/** Extension of compiled java files, i.e. "class" */
|
||||
public final static String EXT_CLASS = "class"; //$NON-NLS-1$
|
||||
/** Extension of xml files, i.e. "xml" */
|
||||
public final static String EXT_XML = "xml"; //$NON-NLS-1$
|
||||
/** Extension of jar files, i.e. "jar" */
|
||||
public final static String EXT_JAR = "jar"; //$NON-NLS-1$
|
||||
/** Extension of aidl files, i.e. "aidl" */
|
||||
public final static String EXT_AIDL = "aidl"; //$NON-NLS-1$
|
||||
/** Extension of native libraries, i.e. "so" */
|
||||
public final static String EXT_NATIVE_LIB = "so"; //$NON-NLS-1$
|
||||
|
||||
private final static String DOT = "."; //$NON-NLS-1$
|
||||
|
||||
/** Dot-Extension of the Application package Files, i.e. ".apk". */
|
||||
public final static String DOT_ANDROID_PACKAGE = DOT + EXT_ANDROID_PACKAGE;
|
||||
/** Dot-Extension of java files, i.e. ".java" */
|
||||
public final static String DOT_JAVA = DOT + EXT_JAVA;
|
||||
/** Dot-Extension of compiled java files, i.e. ".class" */
|
||||
public final static String DOT_CLASS = DOT + EXT_CLASS;
|
||||
/** Dot-Extension of xml files, i.e. ".xml" */
|
||||
public final static String DOT_XML = DOT + EXT_XML;
|
||||
/** Dot-Extension of jar files, i.e. ".jar" */
|
||||
public final static String DOT_JAR = DOT + EXT_JAR;
|
||||
/** Dot-Extension of aidl files, i.e. ".aidl" */
|
||||
public final static String DOT_AIDL = DOT + EXT_AIDL;
|
||||
|
||||
/** Name of the manifest file, i.e. "AndroidManifest.xml". */
|
||||
public static final String FN_ANDROID_MANIFEST = "AndroidManifest.xml"; //$NON-NLS-1$
|
||||
public static final String FN_PROJECT_AIDL = "project.aidl"; //$NON-NLS-1$
|
||||
|
||||
/** dex.jar file */
|
||||
public static final String FN_DX_JAR = "dx.jar"; //$NON-NLS-1$
|
||||
/** Name of the android sources directory */
|
||||
public static final String FD_ANDROID_SOURCES = "sources"; //$NON-NLS-1$
|
||||
|
||||
/** Resource java class filename, i.e. "R.java" */
|
||||
public final static String FN_RESOURCE_CLASS = "R.java"; //$NON-NLS-1$
|
||||
/** Resource class file filename, i.e. "R.class" */
|
||||
public final static String FN_COMPILED_RESOURCE_CLASS = "R.class"; //$NON-NLS-1$
|
||||
/** Manifest java class filename, i.e. "Manifest.java" */
|
||||
public final static String FN_MANIFEST_CLASS = "Manifest.java"; //$NON-NLS-1$
|
||||
/** Dex conversion output filname, i.e. "classes.dex" */
|
||||
public final static String FN_CLASSES_DEX = "classes.dex"; //$NON-NLS-1$
|
||||
/** Temporary packaged resources file name, i.e. "resources.ap_" */
|
||||
public final static String FN_RESOURCES_AP_ = "resources.ap_"; //$NON-NLS-1$
|
||||
|
||||
public final static String FN_ADB = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
|
||||
"adb.exe" : "adb"; //$NON-NLS-1$ //$NON-NLS-2$
|
||||
|
||||
public final static String FN_AAPT = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
|
||||
"aapt.exe" : "aapt"; //$NON-NLS-1$ //$NON-NLS-2$
|
||||
|
||||
public final static String FN_AIDL = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
|
||||
"aidl.exe" : "aidl"; //$NON-NLS-1$ //$NON-NLS-2$
|
||||
|
||||
public final static String FN_EMULATOR = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
|
||||
"emulator.exe" : "emulator"; //$NON-NLS-1$ //$NON-NLS-2$
|
||||
|
||||
public final static String FN_TRACEVIEW = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
|
||||
"traceview.exe" : "traceview"; //$NON-NLS-1$ //$NON-NLS-2$
|
||||
|
||||
/** Folder Names for Android Projects . */
|
||||
|
||||
/* Resources folder name, i.e. "res". */
|
||||
public final static String FD_RESOURCES = "res"; //$NON-NLS-1$
|
||||
/** Assets folder name, i.e. "assets" */
|
||||
public final static String FD_ASSETS = "assets"; //$NON-NLS-1$
|
||||
/** Default source folder name, i.e. "src" */
|
||||
public final static String FD_SOURCES = "src"; //$NON-NLS-1$
|
||||
/** Default native library folder name inside the project, i.e. "libs"
|
||||
* While the folder inside the .apk is "lib", we call that one libs because
|
||||
* that's what we use in ant for both .jar and .so and we need to make the 2 development ways
|
||||
* compatible. */
|
||||
public final static String FD_NATIVE_LIBS = "libs"; //$NON-NLS-1$
|
||||
/** Native lib folder inside the APK: "lib" */
|
||||
public final static String FD_APK_NATIVE_LIBS = "lib"; //$NON-NLS-1$
|
||||
/** Default bin folder name, i.e. "bin" */
|
||||
public final static String FD_BINARIES = "bin"; //$NON-NLS-1$
|
||||
/** Default anim resource folder name, i.e. "anim" */
|
||||
public final static String FD_ANIM = "anim"; //$NON-NLS-1$
|
||||
/** Default color resource folder name, i.e. "color" */
|
||||
public final static String FD_COLOR = "color"; //$NON-NLS-1$
|
||||
/** Default drawable resource folder name, i.e. "drawable" */
|
||||
public final static String FD_DRAWABLE = "drawable"; //$NON-NLS-1$
|
||||
/** Default layout resource folder name, i.e. "layout" */
|
||||
public final static String FD_LAYOUT = "layout"; //$NON-NLS-1$
|
||||
/** Default menu resource folder name, i.e. "menu" */
|
||||
public final static String FD_MENU = "menu"; //$NON-NLS-1$
|
||||
/** Default values resource folder name, i.e. "values" */
|
||||
public final static String FD_VALUES = "values"; //$NON-NLS-1$
|
||||
/** Default xml resource folder name, i.e. "xml" */
|
||||
public final static String FD_XML = "xml"; //$NON-NLS-1$
|
||||
/** Default raw resource folder name, i.e. "raw" */
|
||||
public final static String FD_RAW = "raw"; //$NON-NLS-1$
|
||||
|
||||
/** Absolute path of the workspace root, i.e. "/" */
|
||||
public final static String WS_ROOT = WS_SEP;
|
||||
|
||||
/** Absolute path of the resource folder, eg "/res".<br> This is a workspace path. */
|
||||
public final static String WS_RESOURCES = WS_SEP + FD_RESOURCES;
|
||||
|
||||
/** Absolute path of the resource folder, eg "/assets".<br> This is a workspace path. */
|
||||
public final static String WS_ASSETS = WS_SEP + FD_ASSETS;
|
||||
|
||||
/** Leaf of the javaDoc folder. Does not start with a separator. */
|
||||
public final static String WS_JAVADOC_FOLDER_LEAF = SdkConstants.FD_DOCS + "/reference"; //$NON-NLS-1$
|
||||
|
||||
/** Path of the samples directory relative to the sdk folder.
|
||||
* This is an OS path, ending with a separator.
|
||||
* FIXME: remove once the NPW is fixed. */
|
||||
public final static String OS_SDK_SAMPLES_FOLDER = SdkConstants.FD_SAMPLES + File.separator;
|
||||
|
||||
/** Path of the dx.jar file relative to the sdk folder. */
|
||||
public final static String OS_SDK_LIBS_DX_JAR =
|
||||
SdkConstants.OS_SDK_TOOLS_LIB_FOLDER + FN_DX_JAR;
|
||||
|
||||
/** Regexp for single dot */
|
||||
public final static String RE_DOT = "\\."; //$NON-NLS-1$
|
||||
/** Regexp for java extension, i.e. "\.java$" */
|
||||
public final static String RE_JAVA_EXT = "\\.java$"; //$NON-NLS-1$
|
||||
/** Regexp for aidl extension, i.e. "\.aidl$" */
|
||||
public final static String RE_AIDL_EXT = "\\.aidl$"; //$NON-NLS-1$
|
||||
|
||||
/** Namespace for the resource XML, i.e. "http://schemas.android.com/apk/res/android" */
|
||||
public final static String NS_RESOURCES = "http://schemas.android.com/apk/res/android"; //$NON-NLS-1$
|
||||
|
||||
/** Namespace pattern for the custom resource XML, i.e. "http://schemas.android.com/apk/res/%s" */
|
||||
public final static String NS_CUSTOM_RESOURCES = "http://schemas.android.com/apk/res/%1$s"; //$NON-NLS-1$
|
||||
|
||||
/** The old common plug-in ID. Please do not use for new features. */
|
||||
public static final String COMMON_PLUGIN_ID = "com.android.ide.eclipse.common"; //$NON-NLS-1$
|
||||
|
||||
/** aapt marker error. */
|
||||
public final static String MARKER_AAPT = COMMON_PLUGIN_ID + ".aaptProblem"; //$NON-NLS-1$
|
||||
|
||||
/** XML marker error. */
|
||||
public final static String MARKER_XML = COMMON_PLUGIN_ID + ".xmlProblem"; //$NON-NLS-1$
|
||||
|
||||
/** aidl marker error. */
|
||||
public final static String MARKER_AIDL = COMMON_PLUGIN_ID + ".aidlProblem"; //$NON-NLS-1$
|
||||
|
||||
/** android marker error */
|
||||
public final static String MARKER_ANDROID = COMMON_PLUGIN_ID + ".androidProblem"; //$NON-NLS-1$
|
||||
|
||||
/** Name for the "type" marker attribute */
|
||||
public final static String MARKER_ATTR_TYPE = "android.type"; //$NON-NLS-1$
|
||||
/** Name for the "class" marker attribute */
|
||||
public final static String MARKER_ATTR_CLASS = "android.class"; //$NON-NLS-1$
|
||||
/** activity value for marker attribute "type" */
|
||||
public final static String MARKER_ATTR_TYPE_ACTIVITY = "activity"; //$NON-NLS-1$
|
||||
/** service value for marker attribute "type" */
|
||||
public final static String MARKER_ATTR_TYPE_SERVICE = "service"; //$NON-NLS-1$
|
||||
/** receiver value for marker attribute "type" */
|
||||
public final static String MARKER_ATTR_TYPE_RECEIVER = "receiver"; //$NON-NLS-1$
|
||||
/** provider value for marker attribute "type" */
|
||||
public final static String MARKER_ATTR_TYPE_PROVIDER = "provider"; //$NON-NLS-1$
|
||||
|
||||
public final static String CLASS_ACTIVITY = "android.app.Activity"; //$NON-NLS-1$
|
||||
public final static String CLASS_SERVICE = "android.app.Service"; //$NON-NLS-1$
|
||||
public final static String CLASS_BROADCASTRECEIVER = "android.content.BroadcastReceiver"; //$NON-NLS-1$
|
||||
public final static String CLASS_CONTENTPROVIDER = "android.content.ContentProvider"; //$NON-NLS-1$
|
||||
public final static String CLASS_INSTRUMENTATION = "android.app.Instrumentation"; //$NON-NLS-1$
|
||||
public final static String CLASS_BUNDLE = "android.os.Bundle"; //$NON-NLS-1$
|
||||
public final static String CLASS_R = "android.R"; //$NON-NLS-1$
|
||||
public final static String CLASS_MANIFEST_PERMISSION = "android.Manifest$permission"; //$NON-NLS-1$
|
||||
public final static String CLASS_INTENT = "android.content.Intent"; //$NON-NLS-1$
|
||||
public final static String CLASS_CONTEXT = "android.content.Context"; //$NON-NLS-1$
|
||||
public final static String CLASS_VIEW = "android.view.View"; //$NON-NLS-1$
|
||||
public final static String CLASS_VIEWGROUP = "android.view.ViewGroup"; //$NON-NLS-1$
|
||||
public final static String CLASS_LAYOUTPARAMS = "LayoutParams"; //$NON-NLS-1$
|
||||
public final static String CLASS_VIEWGROUP_LAYOUTPARAMS =
|
||||
CLASS_VIEWGROUP + "$" + CLASS_LAYOUTPARAMS; //$NON-NLS-1$
|
||||
public final static String CLASS_FRAMELAYOUT = "FrameLayout"; //$NON-NLS-1$
|
||||
public final static String CLASS_PREFERENCE = "android.preference.Preference"; //$NON-NLS-1$
|
||||
public final static String CLASS_PREFERENCE_SCREEN = "PreferenceScreen"; //$NON-NLS-1$
|
||||
public final static String CLASS_PREFERENCES =
|
||||
"android.preference." + CLASS_PREFERENCE_SCREEN; //$NON-NLS-1$
|
||||
public final static String CLASS_PREFERENCEGROUP = "android.preference.PreferenceGroup"; //$NON-NLS-1$
|
||||
public final static String CLASS_PARCELABLE = "android.os.Parcelable"; //$NON-NLS-1$
|
||||
|
||||
public final static String CLASS_BRIDGE = "com.android.layoutlib.bridge.Bridge"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Prefered compiler level, i.e. "1.5".
|
||||
*/
|
||||
public final static String COMPILER_COMPLIANCE_PREFERRED = "1.5"; //$NON-NLS-1$
|
||||
/**
|
||||
* List of valid compiler level, i.e. "1.5" and "1.6"
|
||||
*/
|
||||
public final static String[] COMPILER_COMPLIANCE = {
|
||||
"1.5", //$NON-NLS-1$
|
||||
"1.6", //$NON-NLS-1$
|
||||
};
|
||||
|
||||
/** The base URL where to find the Android class & manifest documentation */
|
||||
public static final String CODESITE_BASE_URL = "http://code.google.com/android"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Returns current platform
|
||||
*
|
||||
* @return one of {@link #PLATFORM_WINDOWS}, {@link #PLATFORM_DARWIN},
|
||||
* {@link #PLATFORM_LINUX} or {@link #PLATFORM_UNKNOWN}.
|
||||
*/
|
||||
private static int currentPlatform() {
|
||||
String os = System.getProperty("os.name"); //$NON-NLS-1$
|
||||
if (os.startsWith("Mac OS")) { //$NON-NLS-1$
|
||||
return PLATFORM_DARWIN;
|
||||
} else if (os.startsWith("Windows")) { //$NON-NLS-1$
|
||||
return PLATFORM_WINDOWS;
|
||||
} else if (os.startsWith("Linux")) { //$NON-NLS-1$
|
||||
return PLATFORM_LINUX;
|
||||
}
|
||||
|
||||
return PLATFORM_UNKNOWN;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.common;
|
||||
|
||||
import org.eclipse.ui.IViewPart;
|
||||
import org.eclipse.ui.IWorkbenchPage;
|
||||
import org.eclipse.ui.IWorkbenchWindow;
|
||||
import org.eclipse.ui.PartInitException;
|
||||
import org.eclipse.ui.PlatformUI;
|
||||
|
||||
/**
|
||||
* Helpers for Eclipse UI related stuff.
|
||||
*/
|
||||
public final class EclipseUiHelper {
|
||||
|
||||
/** View Id for the default Eclipse Content Outline view. */
|
||||
public static final String CONTENT_OUTLINE_VIEW_ID = "org.eclipse.ui.views.ContentOutline";
|
||||
/** View Id for the default Eclipse Property Sheet view. */
|
||||
public static final String PROPERTY_SHEET_VIEW_ID = "org.eclipse.ui.views.PropertySheet";
|
||||
|
||||
/** This class never gets instantiated. */
|
||||
private EclipseUiHelper() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the corresponding view.
|
||||
* <p/>
|
||||
* Silently fails in case of error.
|
||||
*
|
||||
* @param viewId One of {@link #CONTENT_OUTLINE_VIEW_ID}, {@link #PROPERTY_SHEET_VIEW_ID}.
|
||||
* @param activate True to force activate (i.e. takes focus), false to just make visible (i.e.
|
||||
* does not steal focus.)
|
||||
*/
|
||||
public static void showView(String viewId, boolean activate) {
|
||||
IWorkbenchWindow win = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
|
||||
if (win != null) {
|
||||
IWorkbenchPage page = win.getActivePage();
|
||||
if (page != null) {
|
||||
try {
|
||||
IViewPart part = page.showView(viewId,
|
||||
null /* secondaryId */,
|
||||
activate ? IWorkbenchPage.VIEW_ACTIVATE : IWorkbenchPage.VIEW_VISIBLE);
|
||||
} catch (PartInitException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
|
||||
package com.android.ide.eclipse.common;
|
||||
|
||||
import org.eclipse.osgi.util.NLS;
|
||||
|
||||
public class Messages extends NLS {
|
||||
private static final String BUNDLE_NAME = "com.android.ide.eclipse.common.messages"; //$NON-NLS-1$
|
||||
|
||||
public static String Console_Data_Project_Tag;
|
||||
|
||||
public static String Console_Date_Tag;
|
||||
|
||||
|
||||
static {
|
||||
// initialize resource bundle
|
||||
NLS.initializeMessages(BUNDLE_NAME, Messages.class);
|
||||
}
|
||||
|
||||
private Messages() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.common;
|
||||
|
||||
import com.android.sdkstats.SdkStatsService;
|
||||
|
||||
import org.osgi.framework.Version;
|
||||
|
||||
/**
|
||||
* Helper class to access the ping usage stat server.
|
||||
*/
|
||||
public class SdkStatsHelper {
|
||||
|
||||
/**
|
||||
* Pings the usage start server.
|
||||
* @param pluginName the name of the plugin to appear in the stats
|
||||
* @param pluginVersion the {@link Version} of the plugin.
|
||||
*/
|
||||
public static void pingUsageServer(String pluginName, Version pluginVersion) {
|
||||
String versionString = String.format("%1$d.%2$d.%3$d", pluginVersion.getMajor(),
|
||||
pluginVersion.getMinor(), pluginVersion.getMicro());
|
||||
|
||||
SdkStatsService.ping(pluginName, versionString);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.common;
|
||||
|
||||
import org.eclipse.ui.console.MessageConsoleStream;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
/**
|
||||
* Stream helper class.
|
||||
*/
|
||||
public class StreamHelper {
|
||||
|
||||
/**
|
||||
* Prints messages, associated with a project to the specified stream
|
||||
* @param stream The stream to write to
|
||||
* @param tag The tag associated to the message. Can be null
|
||||
* @param objects The objects to print through their toString() method (or directly for
|
||||
* {@link String} objects.
|
||||
*/
|
||||
public static synchronized void printToStream(MessageConsoleStream stream, String tag,
|
||||
Object... objects) {
|
||||
String dateTag = getMessageTag(tag);
|
||||
|
||||
for (Object obj : objects) {
|
||||
stream.print(dateTag);
|
||||
if (obj instanceof String) {
|
||||
stream.println((String)obj);
|
||||
} else {
|
||||
stream.println(obj.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string containing the current date/time, and the tag
|
||||
* @param tag The tag associated to the message. Can be null
|
||||
* @return The dateTag
|
||||
*/
|
||||
public static String getMessageTag(String tag) {
|
||||
Calendar c = Calendar.getInstance();
|
||||
|
||||
if (tag == null) {
|
||||
return String.format(Messages.Console_Date_Tag, c);
|
||||
}
|
||||
|
||||
return String.format(Messages.Console_Data_Project_Tag, c, tag);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
Console_Date_Tag=[%1$tF %1$tT]
|
||||
Console_Data_Project_Tag=[%1$tF %1$tT - %2$s]
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.common.preferences;
|
||||
|
||||
import com.android.sdkstats.SdkStatsService;
|
||||
|
||||
import org.eclipse.jface.preference.BooleanFieldEditor;
|
||||
import org.eclipse.jface.preference.PreferencePage;
|
||||
import org.eclipse.jface.preference.PreferenceStore;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.SelectionAdapter;
|
||||
import org.eclipse.swt.events.SelectionEvent;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.eclipse.swt.widgets.Link;
|
||||
import org.eclipse.ui.IWorkbench;
|
||||
import org.eclipse.ui.IWorkbenchPreferencePage;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class UsagePreferencePage extends PreferencePage implements IWorkbenchPreferencePage {
|
||||
|
||||
private BooleanFieldEditor mOptInCheckBox;
|
||||
|
||||
public UsagePreferencePage() {
|
||||
}
|
||||
|
||||
public void init(IWorkbench workbench) {
|
||||
// pass
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Control createContents(Composite parent) {
|
||||
Composite top = new Composite(parent, SWT.NONE);
|
||||
top.setLayout(new GridLayout(1, false));
|
||||
top.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
|
||||
Link text = new Link(top, SWT.WRAP);
|
||||
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
|
||||
gd.widthHint = 200;
|
||||
text.setLayoutData(gd);
|
||||
text.setText(SdkStatsService.BODY_TEXT);
|
||||
|
||||
text.addSelectionListener(new SelectionAdapter() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent event) {
|
||||
SdkStatsService.openUrl(event.text);
|
||||
}
|
||||
});
|
||||
|
||||
mOptInCheckBox = new BooleanFieldEditor(SdkStatsService.PING_OPT_IN,
|
||||
SdkStatsService.CHECKBOX_TEXT, top);
|
||||
mOptInCheckBox.setPage(this);
|
||||
mOptInCheckBox.setPreferenceStore(SdkStatsService.getPreferenceStore());
|
||||
mOptInCheckBox.load();
|
||||
|
||||
return top;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.eclipse.jface.preference.PreferencePage#performCancel()
|
||||
*/
|
||||
@Override
|
||||
public boolean performCancel() {
|
||||
mOptInCheckBox.load();
|
||||
return super.performCancel();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.eclipse.jface.preference.PreferencePage#performDefaults()
|
||||
*/
|
||||
@Override
|
||||
protected void performDefaults() {
|
||||
mOptInCheckBox.loadDefault();
|
||||
super.performDefaults();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.eclipse.jface.preference.PreferencePage#performOk()
|
||||
*/
|
||||
@Override
|
||||
public boolean performOk() {
|
||||
save();
|
||||
return super.performOk();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.eclipse.jface.preference.PreferencePage#performApply()
|
||||
*/
|
||||
@Override
|
||||
protected void performApply() {
|
||||
save();
|
||||
super.performApply();
|
||||
}
|
||||
|
||||
private void save() {
|
||||
try {
|
||||
PreferenceStore store = SdkStatsService.getPreferenceStore();
|
||||
if (store != null) {
|
||||
store.setValue(SdkStatsService.PING_OPT_IN, mOptInCheckBox.getBooleanValue());
|
||||
store.save();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.common.project;
|
||||
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
|
||||
/**
|
||||
* Utility class that manages the AndroidManifest.xml file.
|
||||
* <p/>
|
||||
* All the get method work by XPath. Repeated calls to those may warrant using
|
||||
* {@link AndroidManifestParser} instead.
|
||||
*/
|
||||
public class AndroidManifestHelper {
|
||||
private IFile mManifestIFile;
|
||||
private File mManifestFile;
|
||||
private XPath mXPath;
|
||||
|
||||
/**
|
||||
* Creates an AndroidManifest based on an existing Eclipse {@link IProject} object.
|
||||
* </p>
|
||||
* Use {@link #exists()} to check if the manifest file really exists in the project.
|
||||
*
|
||||
* @param project The project to search for the manifest.
|
||||
*/
|
||||
public AndroidManifestHelper(IProject project) {
|
||||
mXPath = AndroidXPathFactory.newXPath();
|
||||
mManifestIFile = getManifest(project);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an AndroidManifest based on a file path.
|
||||
* <p/>
|
||||
* Use {@link #exists()} to check if the manifest file really exists.
|
||||
*
|
||||
* @param osManifestFilePath the os path to the AndroidManifest.xml file.
|
||||
*/
|
||||
public AndroidManifestHelper(String osManifestFilePath) {
|
||||
mXPath = AndroidXPathFactory.newXPath();
|
||||
mManifestFile = new File(osManifestFilePath);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the underlying {@link IFile} for the android manifest XML file, if found in the
|
||||
* given Eclipse project.
|
||||
*
|
||||
* Always return null if the constructor that takes an {@link IProject} was NOT called.
|
||||
*
|
||||
* @return The IFile for the androidManifest.xml or null if no such file could be found.
|
||||
*/
|
||||
public IFile getManifestIFile() {
|
||||
return mManifestIFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying {@link File} for the android manifest XML file.
|
||||
*/
|
||||
public File getManifestFile() {
|
||||
if (mManifestIFile != null) {
|
||||
return mManifestIFile.getLocation().toFile();
|
||||
}
|
||||
|
||||
return mManifestFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the package name defined in the manifest file.
|
||||
*
|
||||
* @return A String object with the package or null if any error happened.
|
||||
*/
|
||||
public String getPackageName() {
|
||||
try {
|
||||
return getPackageNameInternal(mXPath, getSource());
|
||||
} catch (XPathExpressionException e1) {
|
||||
// If the XPath failed to evaluate, we'll return null.
|
||||
} catch (Exception e) {
|
||||
// if this happens this is due to the resource being out of sync.
|
||||
// so we must refresh it and do it again
|
||||
|
||||
// for any other kind of exception we must return null as well;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the i-th activity defined in the manifest file.
|
||||
*
|
||||
* @param manifest The manifest's IFile object.
|
||||
* @param index The 1-based index of the activity to return.
|
||||
* @param xpath An optional xpath object. If null is provided a new one will
|
||||
* be created.
|
||||
* @return A String object with the activity or null if any error happened.
|
||||
*/
|
||||
public String getActivityName(int index) {
|
||||
try {
|
||||
return getActivityNameInternal(index, mXPath, getSource());
|
||||
} catch (XPathExpressionException e1) {
|
||||
// If the XPath failed to evaluate, we'll return null.
|
||||
} catch (Exception e) {
|
||||
// if this happens this is due to the resource being out of sync.
|
||||
// so we must refresh it and do it again
|
||||
|
||||
// for any other kind of exception we must return null as well;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an IFile object representing the manifest for the specified
|
||||
* project.
|
||||
*
|
||||
* @param project The project containing the manifest file.
|
||||
* @return An IFile object pointing to the manifest or null if the manifest
|
||||
* is missing.
|
||||
*/
|
||||
public static IFile getManifest(IProject project) {
|
||||
IResource r = project.findMember(AndroidConstants.WS_SEP
|
||||
+ AndroidConstants.FN_ANDROID_MANIFEST);
|
||||
|
||||
if (r == null || r.exists() == false || (r instanceof IFile) == false) {
|
||||
return null;
|
||||
}
|
||||
return (IFile) r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines a java package, with a class value from the manifest to make a fully qualified
|
||||
* class name
|
||||
* @param javaPackage the java package from the manifest.
|
||||
* @param className the class name from the manifest.
|
||||
* @return the fully qualified class name.
|
||||
*/
|
||||
public static String combinePackageAndClassName(String javaPackage, String className) {
|
||||
if (className == null || className.length() == 0) {
|
||||
return javaPackage;
|
||||
}
|
||||
if (javaPackage == null || javaPackage.length() == 0) {
|
||||
return className;
|
||||
}
|
||||
|
||||
// the class name can be a subpackage (starts with a '.'
|
||||
// char), a simple class name (no dot), or a full java package
|
||||
boolean startWithDot = (className.charAt(0) == '.');
|
||||
boolean hasDot = (className.indexOf('.') != -1);
|
||||
if (startWithDot || hasDot == false) {
|
||||
|
||||
// add the concatenation of the package and class name
|
||||
if (startWithDot) {
|
||||
return javaPackage + className;
|
||||
} else {
|
||||
return javaPackage + '.' + className;
|
||||
}
|
||||
} else {
|
||||
// just add the class as it should be a fully qualified java name.
|
||||
return className;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns true either if an androidManifest.xml file was found in the project
|
||||
* or if the given file path exists.
|
||||
*/
|
||||
public boolean exists() {
|
||||
if (mManifestIFile != null) {
|
||||
return mManifestIFile.exists();
|
||||
} else if (mManifestFile != null) {
|
||||
return mManifestFile.exists();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an InputSource for XPath.
|
||||
*
|
||||
* @throws FileNotFoundException if file does not exist.
|
||||
* @throws CoreException if the {@link IFile} does not exist.
|
||||
*/
|
||||
private InputSource getSource() throws FileNotFoundException, CoreException {
|
||||
if (mManifestIFile != null) {
|
||||
return new InputSource(mManifestIFile.getContents());
|
||||
} else if (mManifestFile != null) {
|
||||
return new InputSource(new FileReader(mManifestFile));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the actual XPath evaluation to get the package name.
|
||||
* Extracted so that we can share it with AndroidManifestFromProject.
|
||||
*/
|
||||
private static String getPackageNameInternal(XPath xpath, InputSource source)
|
||||
throws XPathExpressionException {
|
||||
return xpath.evaluate("/manifest/@package", source); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the actual XPath evaluation to get the activity name.
|
||||
* Extracted so that we can share it with AndroidManifestFromProject.
|
||||
*/
|
||||
private static String getActivityNameInternal(int index, XPath xpath, InputSource source)
|
||||
throws XPathExpressionException {
|
||||
return xpath.evaluate("/manifest/application/activity[" //$NON-NLS-1$
|
||||
+ index
|
||||
+ "]/@" //$NON-NLS-1$
|
||||
+ AndroidXPathFactory.DEFAULT_NS_PREFIX +":name", //$NON-NLS-1$
|
||||
source);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,666 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.common.project;
|
||||
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.common.project.XmlErrorHandler.XmlErrorListener;
|
||||
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.resources.IMarker;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.Locator;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.SAXParseException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
|
||||
public class AndroidManifestParser {
|
||||
|
||||
private final static String ATTRIBUTE_PACKAGE = "package"; //$NON-NLS-1$
|
||||
private final static String ATTRIBUTE_NAME = "name"; //$NON-NLS-1$
|
||||
private final static String ATTRIBUTE_PROCESS = "process"; //$NON-NLS-$
|
||||
private final static String ATTRIBUTE_DEBUGGABLE = "debuggable"; //$NON-NLS-$
|
||||
private final static String ATTRIBUTE_MIN_SDK_VERSION = "minSdkVersion"; //$NON-NLS-$
|
||||
private final static String NODE_MANIFEST = "manifest"; //$NON-NLS-1$
|
||||
private final static String NODE_APPLICATION = "application"; //$NON-NLS-1$
|
||||
private final static String NODE_ACTIVITY = "activity"; //$NON-NLS-1$
|
||||
private final static String NODE_SERVICE = "service"; //$NON-NLS-1$
|
||||
private final static String NODE_RECEIVER = "receiver"; //$NON-NLS-1$
|
||||
private final static String NODE_PROVIDER = "provider"; //$NON-NLS-1$
|
||||
private final static String NODE_INTENT = "intent-filter"; //$NON-NLS-1$
|
||||
private final static String NODE_ACTION = "action"; //$NON-NLS-1$
|
||||
private final static String NODE_CATEGORY = "category"; //$NON-NLS-1$
|
||||
private final static String NODE_USES_SDK = "uses-sdk"; //$NON-NLS-1$
|
||||
|
||||
private final static int LEVEL_MANIFEST = 0;
|
||||
private final static int LEVEL_APPLICATION = 1;
|
||||
private final static int LEVEL_ACTIVITY = 2;
|
||||
private final static int LEVEL_INTENT_FILTER = 3;
|
||||
private final static int LEVEL_CATEGORY = 4;
|
||||
|
||||
private final static String ACTION_MAIN = "android.intent.action.MAIN"; //$NON-NLS-1$
|
||||
private final static String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER"; //$NON-NLS-1$
|
||||
|
||||
private static class ManifestHandler extends XmlErrorHandler {
|
||||
|
||||
//--- data read from the parsing
|
||||
|
||||
/** Application package */
|
||||
private String mPackage;
|
||||
/** List of all activities */
|
||||
private final ArrayList<String> mActivities = new ArrayList<String>();
|
||||
/** Launcher activity */
|
||||
private String mLauncherActivity = null;
|
||||
/** list of process names declared by the manifest */
|
||||
private Set<String> mProcesses = null;
|
||||
/** debuggable attribute value. If null, the attribute is not present. */
|
||||
private Boolean mDebuggable = null;
|
||||
/** API level requirement. if 0 the attribute was not present. */
|
||||
private int mApiLevelRequirement = 0;
|
||||
|
||||
//--- temporary data/flags used during parsing
|
||||
private IJavaProject mJavaProject;
|
||||
private boolean mGatherData = false;
|
||||
private boolean mMarkErrors = false;
|
||||
private int mCurrentLevel = 0;
|
||||
private int mValidLevel = 0;
|
||||
private boolean mFoundMainAction = false;
|
||||
private boolean mFoundLauncherCategory = false;
|
||||
private String mCurrentActivity = null;
|
||||
private Locator mLocator;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param manifestFile
|
||||
* @param errorListener
|
||||
* @param gatherData
|
||||
* @param javaProject
|
||||
* @param markErrors
|
||||
*/
|
||||
ManifestHandler(IFile manifestFile, XmlErrorListener errorListener,
|
||||
boolean gatherData, IJavaProject javaProject, boolean markErrors) {
|
||||
super(manifestFile, errorListener);
|
||||
mGatherData = gatherData;
|
||||
mJavaProject = javaProject;
|
||||
mMarkErrors = markErrors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the package defined in the manifest, if found.
|
||||
* @return The package name or null if not found.
|
||||
*/
|
||||
String getPackage() {
|
||||
return mPackage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of activities found in the manifest.
|
||||
* @return An array of fully qualified class names, or empty if no activity were found.
|
||||
*/
|
||||
String[] getActivities() {
|
||||
return mActivities.toArray(new String[mActivities.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of one activity found in the manifest, that is configured to show
|
||||
* up in the HOME screen.
|
||||
* @return the fully qualified name of a HOME activity or null if none were found.
|
||||
*/
|
||||
String getLauncherActivity() {
|
||||
return mLauncherActivity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of process names declared by the manifest.
|
||||
*/
|
||||
String[] getProcesses() {
|
||||
if (mProcesses != null) {
|
||||
return mProcesses.toArray(new String[mProcesses.size()]);
|
||||
}
|
||||
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the <code>debuggable</code> attribute value or null if it is not set.
|
||||
*/
|
||||
Boolean getDebuggable() {
|
||||
return mDebuggable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the <code>minSdkVersion</code> attribute, or 0 if it's not set.
|
||||
*/
|
||||
int getApiLevelRequirement() {
|
||||
return mApiLevelRequirement;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.xml.sax.helpers.DefaultHandler#setDocumentLocator(org.xml.sax.Locator)
|
||||
*/
|
||||
@Override
|
||||
public void setDocumentLocator(Locator locator) {
|
||||
mLocator = locator;
|
||||
super.setDocumentLocator(locator);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String,
|
||||
* java.lang.String, org.xml.sax.Attributes)
|
||||
*/
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String name, Attributes attributes)
|
||||
throws SAXException {
|
||||
try {
|
||||
if (mGatherData == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if we're at a valid level
|
||||
if (mValidLevel == mCurrentLevel) {
|
||||
String value;
|
||||
switch (mValidLevel) {
|
||||
case LEVEL_MANIFEST:
|
||||
if (NODE_MANIFEST.equals(localName)) {
|
||||
// lets get the package name.
|
||||
mPackage = getAttributeValue(attributes, ATTRIBUTE_PACKAGE,
|
||||
false /* hasNamespace */);
|
||||
mValidLevel++;
|
||||
}
|
||||
break;
|
||||
case LEVEL_APPLICATION:
|
||||
if (NODE_APPLICATION.equals(localName)) {
|
||||
value = getAttributeValue(attributes, ATTRIBUTE_PROCESS,
|
||||
true /* hasNamespace */);
|
||||
if (value != null) {
|
||||
addProcessName(value);
|
||||
}
|
||||
|
||||
value = getAttributeValue(attributes, ATTRIBUTE_DEBUGGABLE,
|
||||
true /* hasNamespace*/);
|
||||
if (value != null) {
|
||||
mDebuggable = Boolean.parseBoolean(value);
|
||||
}
|
||||
|
||||
mValidLevel++;
|
||||
} else if (NODE_USES_SDK.equals(localName)) {
|
||||
value = getAttributeValue(attributes, ATTRIBUTE_MIN_SDK_VERSION,
|
||||
true /* hasNamespace */);
|
||||
|
||||
try {
|
||||
mApiLevelRequirement = Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) {
|
||||
handleError(e, -1 /* lineNumber */);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LEVEL_ACTIVITY:
|
||||
if (NODE_ACTIVITY.equals(localName)) {
|
||||
processActivityNode(attributes);
|
||||
mValidLevel++;
|
||||
} else if (NODE_SERVICE.equals(localName)) {
|
||||
processNode(attributes, AndroidConstants.CLASS_SERVICE);
|
||||
mValidLevel++;
|
||||
} else if (NODE_RECEIVER.equals(localName)) {
|
||||
processNode(attributes, AndroidConstants.CLASS_BROADCASTRECEIVER);
|
||||
mValidLevel++;
|
||||
} else if (NODE_PROVIDER.equals(localName)) {
|
||||
processNode(attributes, AndroidConstants.CLASS_CONTENTPROVIDER);
|
||||
mValidLevel++;
|
||||
}
|
||||
break;
|
||||
case LEVEL_INTENT_FILTER:
|
||||
// only process this level if we are in an activity
|
||||
if (mCurrentActivity != null && NODE_INTENT.equals(localName)) {
|
||||
// if we're at the intent level, lets reset some flag to
|
||||
// be used when parsing the children
|
||||
mFoundMainAction = false;
|
||||
mFoundLauncherCategory = false;
|
||||
mValidLevel++;
|
||||
}
|
||||
break;
|
||||
case LEVEL_CATEGORY:
|
||||
if (mCurrentActivity != null && mLauncherActivity == null) {
|
||||
if (NODE_ACTION.equals(localName)) {
|
||||
// get the name attribute
|
||||
if (ACTION_MAIN.equals(
|
||||
getAttributeValue(attributes, ATTRIBUTE_NAME,
|
||||
true /* hasNamespace */))) {
|
||||
mFoundMainAction = true;
|
||||
}
|
||||
} else if (NODE_CATEGORY.equals(localName)) {
|
||||
if (CATEGORY_LAUNCHER.equals(
|
||||
getAttributeValue(attributes, ATTRIBUTE_NAME,
|
||||
true /* hasNamespace */))) {
|
||||
mFoundLauncherCategory = true;
|
||||
}
|
||||
}
|
||||
|
||||
// no need to increase mValidLevel as we don't process anything
|
||||
// below this level.
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mCurrentLevel++;
|
||||
} finally {
|
||||
super.startElement(uri, localName, name, attributes);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String,
|
||||
* java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String name) throws SAXException {
|
||||
try {
|
||||
if (mGatherData == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// decrement the levels.
|
||||
if (mValidLevel == mCurrentLevel) {
|
||||
mValidLevel--;
|
||||
}
|
||||
mCurrentLevel--;
|
||||
|
||||
// if we're at a valid level
|
||||
// process the end of the element
|
||||
if (mValidLevel == mCurrentLevel) {
|
||||
switch (mValidLevel) {
|
||||
case LEVEL_ACTIVITY:
|
||||
mCurrentActivity = null;
|
||||
break;
|
||||
case LEVEL_INTENT_FILTER:
|
||||
// if we found both a main action and a launcher category, this is our
|
||||
// launcher activity!
|
||||
if (mCurrentActivity != null &&
|
||||
mFoundMainAction && mFoundLauncherCategory) {
|
||||
mLauncherActivity = mCurrentActivity;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
} finally {
|
||||
super.endElement(uri, localName, name);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.xml.sax.helpers.DefaultHandler#error(org.xml.sax.SAXParseException)
|
||||
*/
|
||||
@Override
|
||||
public void error(SAXParseException e) throws SAXException {
|
||||
if (mMarkErrors) {
|
||||
handleError(e, e.getLineNumber());
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.xml.sax.helpers.DefaultHandler#fatalError(org.xml.sax.SAXParseException)
|
||||
*/
|
||||
@Override
|
||||
public void fatalError(SAXParseException e) throws SAXException {
|
||||
if (mMarkErrors) {
|
||||
handleError(e, e.getLineNumber());
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.xml.sax.helpers.DefaultHandler#warning(org.xml.sax.SAXParseException)
|
||||
*/
|
||||
@Override
|
||||
public void warning(SAXParseException e) throws SAXException {
|
||||
if (mMarkErrors) {
|
||||
super.warning(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the activity node.
|
||||
* @param attributes the attributes for the activity node.
|
||||
* @throws CoreException
|
||||
*/
|
||||
private void processActivityNode(Attributes attributes) {
|
||||
// lets get the activity name, and add it to the list
|
||||
String activityName = getAttributeValue(attributes, ATTRIBUTE_NAME,
|
||||
true /* hasNamespace */);
|
||||
if (activityName != null) {
|
||||
mCurrentActivity = AndroidManifestHelper.combinePackageAndClassName(mPackage,
|
||||
activityName);
|
||||
mActivities.add(mCurrentActivity);
|
||||
|
||||
if (mMarkErrors) {
|
||||
checkClass(mCurrentActivity, AndroidConstants.CLASS_ACTIVITY,
|
||||
true /* testVisibility */);
|
||||
}
|
||||
} else {
|
||||
// no activity found! Aapt will output an error,
|
||||
// so we don't have to do anything
|
||||
mCurrentActivity = activityName;
|
||||
}
|
||||
|
||||
String processName = getAttributeValue(attributes, ATTRIBUTE_PROCESS,
|
||||
true /* hasNamespace */);
|
||||
if (processName != null) {
|
||||
addProcessName(processName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the service/receiver/provider nodes.
|
||||
* @param attributes the attributes for the activity node.
|
||||
* @param superClassName the fully qualified name of the super class that this
|
||||
* node is representing
|
||||
* @throws CoreException
|
||||
*/
|
||||
private void processNode(Attributes attributes, String superClassName) {
|
||||
// lets get the class name, and check it if required.
|
||||
String serviceName = getAttributeValue(attributes, ATTRIBUTE_NAME,
|
||||
true /* hasNamespace */);
|
||||
if (serviceName != null) {
|
||||
serviceName = AndroidManifestHelper.combinePackageAndClassName(mPackage,
|
||||
serviceName);
|
||||
|
||||
if (mMarkErrors) {
|
||||
checkClass(serviceName, superClassName, false /* testVisibility */);
|
||||
}
|
||||
}
|
||||
|
||||
String processName = getAttributeValue(attributes, ATTRIBUTE_PROCESS,
|
||||
true /* hasNamespace */);
|
||||
if (processName != null) {
|
||||
addProcessName(processName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a class is valid and can be used in the Android Manifest.
|
||||
* <p/>
|
||||
* Errors are put as {@link IMarker} on the manifest file.
|
||||
* @param className the fully qualified name of the class to test.
|
||||
* @param superClassName the fully qualified name of the class it is supposed to extend.
|
||||
* @param testVisibility if <code>true</code>, the method will check the visibility of
|
||||
* the class or of its constructors.
|
||||
*/
|
||||
private void checkClass(String className, String superClassName, boolean testVisibility) {
|
||||
// we need to check the validity of the activity.
|
||||
String result = BaseProjectHelper.testClassForManifest(mJavaProject,
|
||||
className, superClassName, testVisibility);
|
||||
if (result != BaseProjectHelper.TEST_CLASS_OK) {
|
||||
// get the line number
|
||||
int line = mLocator.getLineNumber();
|
||||
|
||||
// mark the file
|
||||
IMarker marker = BaseProjectHelper.addMarker(getFile(),
|
||||
AndroidConstants.MARKER_ANDROID,
|
||||
result, line, IMarker.SEVERITY_ERROR);
|
||||
|
||||
// add custom attributes to be used by the manifest editor.
|
||||
if (marker != null) {
|
||||
try {
|
||||
marker.setAttribute(AndroidConstants.MARKER_ATTR_TYPE,
|
||||
AndroidConstants.MARKER_ATTR_TYPE_ACTIVITY);
|
||||
marker.setAttribute(AndroidConstants.MARKER_ATTR_CLASS, className);
|
||||
} catch (CoreException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches through the attributes list for a particular one and returns its value.
|
||||
* @param attributes the attribute list to search through
|
||||
* @param attributeName the name of the attribute to look for.
|
||||
* @param hasNamespace Indicates whether the attribute has an android namespace.
|
||||
* @return a String with the value or null if the attribute was not found.
|
||||
* @see AndroidConstants#NS_RESOURCES
|
||||
*/
|
||||
private String getAttributeValue(Attributes attributes, String attributeName,
|
||||
boolean hasNamespace) {
|
||||
int count = attributes.getLength();
|
||||
for (int i = 0 ; i < count ; i++) {
|
||||
if (attributeName.equals(attributes.getLocalName(i)) &&
|
||||
((hasNamespace &&
|
||||
AndroidConstants.NS_RESOURCES.equals(attributes.getURI(i))) ||
|
||||
(hasNamespace == false && attributes.getURI(i).length() == 0))) {
|
||||
return attributes.getValue(i);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void addProcessName(String processName) {
|
||||
if (mProcesses == null) {
|
||||
mProcesses = new TreeSet<String>();
|
||||
}
|
||||
|
||||
mProcesses.add(processName);
|
||||
}
|
||||
}
|
||||
|
||||
private static SAXParserFactory sParserFactory;
|
||||
|
||||
private final String mJavaPackage;
|
||||
private final String[] mActivities;
|
||||
private final String mLauncherActivity;
|
||||
private final String[] mProcesses;
|
||||
private final Boolean mDebuggable;
|
||||
private final int mApiLevelRequirement;
|
||||
|
||||
static {
|
||||
sParserFactory = SAXParserFactory.newInstance();
|
||||
sParserFactory.setNamespaceAware(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the Android Manifest, and returns an object containing
|
||||
* the result of the parsing.
|
||||
* @param javaProject The java project.
|
||||
* @param manifestFile the {@link IFile} representing the manifest file.
|
||||
* @param errorListener
|
||||
* @param gatherData indicates whether the parsing will extract data from the manifest.
|
||||
* @param markErrors indicates whether the error found during parsing should put a
|
||||
* marker on the file. For class validation errors to put a marker, <code>gatherData</code>
|
||||
* must be set to <code>true</code>
|
||||
* @return an {@link AndroidManifestParser} or null if the parsing failed.
|
||||
* @throws CoreException
|
||||
*/
|
||||
public static AndroidManifestParser parse(IJavaProject javaProject, IFile manifestFile,
|
||||
XmlErrorListener errorListener, boolean gatherData, boolean markErrors)
|
||||
throws CoreException {
|
||||
try {
|
||||
SAXParser parser = sParserFactory.newSAXParser();
|
||||
|
||||
ManifestHandler manifestHandler = new ManifestHandler(manifestFile,
|
||||
errorListener, gatherData, javaProject, markErrors);
|
||||
|
||||
parser.parse(new InputSource(manifestFile.getContents()), manifestHandler);
|
||||
|
||||
// get the result from the handler
|
||||
|
||||
return new AndroidManifestParser(manifestHandler.getPackage(),
|
||||
manifestHandler.getActivities(), manifestHandler.getLauncherActivity(),
|
||||
manifestHandler.getProcesses(), manifestHandler.getDebuggable(),
|
||||
manifestHandler.getApiLevelRequirement());
|
||||
} catch (ParserConfigurationException e) {
|
||||
} catch (SAXException e) {
|
||||
} catch (IOException e) {
|
||||
} finally {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the Android Manifest for the specified project, and returns an object containing
|
||||
* the result of the parsing.
|
||||
* @param javaProject The java project. Required if <var>markErrors</var> is <code>true</code>
|
||||
* @param errorListener the {@link XmlErrorListener} object being notified of the presence
|
||||
* of errors. Optional.
|
||||
* @param gatherData indicates whether the parsing will extract data from the manifest.
|
||||
* @param markErrors indicates whether the error found during parsing should put a
|
||||
* marker on the file. For class validation errors to put a marker, <code>gatherData</code>
|
||||
* must be set to <code>true</code>
|
||||
* @return an {@link AndroidManifestParser} or null if the parsing failed.
|
||||
* @throws CoreException
|
||||
*/
|
||||
public static AndroidManifestParser parse(IJavaProject javaProject,
|
||||
XmlErrorListener errorListener, boolean gatherData, boolean markErrors)
|
||||
throws CoreException {
|
||||
try {
|
||||
SAXParser parser = sParserFactory.newSAXParser();
|
||||
|
||||
IFile manifestFile = AndroidManifestHelper.getManifest(javaProject.getProject());
|
||||
if (manifestFile != null) {
|
||||
ManifestHandler manifestHandler = new ManifestHandler(manifestFile,
|
||||
errorListener, gatherData, javaProject, markErrors);
|
||||
|
||||
parser.parse(new InputSource(manifestFile.getContents()), manifestHandler);
|
||||
|
||||
// get the result from the handler
|
||||
return new AndroidManifestParser(manifestHandler.getPackage(),
|
||||
manifestHandler.getActivities(), manifestHandler.getLauncherActivity(),
|
||||
manifestHandler.getProcesses(), manifestHandler.getDebuggable(),
|
||||
manifestHandler.getApiLevelRequirement());
|
||||
}
|
||||
} catch (ParserConfigurationException e) {
|
||||
} catch (SAXException e) {
|
||||
} catch (IOException e) {
|
||||
} finally {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the manifest file, collects data, and checks for errors.
|
||||
* @param javaProject The java project. Required.
|
||||
* @param manifestFile
|
||||
* @param errorListener the {@link XmlErrorListener} object being notified of the presence
|
||||
* of errors. Optional.
|
||||
* @return an {@link AndroidManifestParser} or null if the parsing failed.
|
||||
* @throws CoreException
|
||||
* @see {@link #parse(IJavaProject, IFile, XmlErrorListener, boolean, boolean)}
|
||||
*/
|
||||
public static AndroidManifestParser parseForError(IJavaProject javaProject, IFile manifestFile,
|
||||
XmlErrorListener errorListener) throws CoreException {
|
||||
return parse(javaProject, manifestFile, errorListener, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the manifest file, and collects data.
|
||||
* @param manifestFile
|
||||
* @param errorListener the {@link XmlErrorListener} object being notified of the presence
|
||||
* of errors. Optional.
|
||||
* @return an {@link AndroidManifestParser} or null if the parsing failed.
|
||||
* @throws CoreException
|
||||
* @see {@link #parse(IJavaProject, IFile, XmlErrorListener, boolean, boolean)}
|
||||
*/
|
||||
public static AndroidManifestParser parseForData(IFile manifestFile) throws CoreException {
|
||||
return parse(null /* javaProject */, manifestFile, null /* errorListener */,
|
||||
true /* gatherData */, false /* markErrors */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the package defined in the manifest, if found.
|
||||
* @return The package name or null if not found.
|
||||
*/
|
||||
public String getPackage() {
|
||||
return mJavaPackage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of activities found in the manifest.
|
||||
* @return An array of fully qualified class names, or empty if no activity were found.
|
||||
*/
|
||||
public String[] getActivities() {
|
||||
return mActivities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of one activity found in the manifest, that is configured to show
|
||||
* up in the HOME screen.
|
||||
* @return the fully qualified name of a HOME activity or null if none were found.
|
||||
*/
|
||||
public String getLauncherActivity() {
|
||||
return mLauncherActivity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of process names declared by the manifest.
|
||||
*/
|
||||
public String[] getProcesses() {
|
||||
return mProcesses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the debuggable attribute value or <code>null</code> if it is not set.
|
||||
*/
|
||||
public Boolean getDebuggable() {
|
||||
return mDebuggable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the <code>minSdkVersion</code> attribute, or 0 if it's not set.
|
||||
*/
|
||||
public int getApiLevelRequirement() {
|
||||
return mApiLevelRequirement;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Private constructor to enforce using
|
||||
* {@link #parse(IJavaProject, XmlErrorListener, boolean, boolean)},
|
||||
* {@link #parse(IJavaProject, IFile, XmlErrorListener, boolean, boolean)},
|
||||
* or {@link #parseForError(IJavaProject, IFile, XmlErrorListener)} to get an
|
||||
* {@link AndroidManifestParser} object.
|
||||
* @param javaPackage the package parsed from the manifest.
|
||||
* @param activities the list of activities parsed from the manifest.
|
||||
* @param launcherActivity the launcher activity parser from the manifest.
|
||||
* @param processes the list of custom processes declared in the manifest.
|
||||
* @param debuggable the debuggable attribute, or null if not set.
|
||||
* @param apiLevelRequirement the minSdkVersion attribute value or 0 if not set.
|
||||
*/
|
||||
private AndroidManifestParser(String javaPackage, String[] activities,
|
||||
String launcherActivity, String[] processes, Boolean debuggable,
|
||||
int apiLevelRequirement) {
|
||||
mJavaPackage = javaPackage;
|
||||
mActivities = activities;
|
||||
mLauncherActivity = launcherActivity;
|
||||
mProcesses = processes;
|
||||
mDebuggable = debuggable;
|
||||
mApiLevelRequirement = apiLevelRequirement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.common.project;
|
||||
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.namespace.NamespaceContext;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
|
||||
/**
|
||||
* XPath factory with automatic support for the android namespace.
|
||||
*/
|
||||
public class AndroidXPathFactory {
|
||||
public final static String DEFAULT_NS_PREFIX = "android"; //$NON-NLS-1$
|
||||
|
||||
private final static XPathFactory sFactory = XPathFactory.newInstance();
|
||||
|
||||
/** Namespace context for Android resource XML files. */
|
||||
private static class AndroidNamespaceContext implements NamespaceContext {
|
||||
private String mAndroidPrefix;
|
||||
|
||||
/**
|
||||
* Construct the context with the prefix associated with the android namespace.
|
||||
* @param prefix the Prefix
|
||||
*/
|
||||
public AndroidNamespaceContext(String androidPrefix) {
|
||||
mAndroidPrefix = androidPrefix;
|
||||
}
|
||||
|
||||
public String getNamespaceURI(String prefix) {
|
||||
if (prefix != null) {
|
||||
if (prefix.equals(mAndroidPrefix)) {
|
||||
return AndroidConstants.NS_RESOURCES;
|
||||
}
|
||||
}
|
||||
|
||||
return XMLConstants.NULL_NS_URI;
|
||||
}
|
||||
|
||||
public String getPrefix(String namespaceURI) {
|
||||
// This isn't necessary for our use.
|
||||
assert false;
|
||||
return null;
|
||||
}
|
||||
|
||||
public Iterator<?> getPrefixes(String namespaceURI) {
|
||||
// This isn't necessary for our use.
|
||||
assert false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new XPath object, specifying which prefix in the query is used for the
|
||||
* android namespace.
|
||||
* @param prefix The namespace prefix.
|
||||
*/
|
||||
public static XPath newXPath(String androidPrefix) {
|
||||
XPath xpath = sFactory.newXPath();
|
||||
xpath.setNamespaceContext(new AndroidNamespaceContext(androidPrefix));
|
||||
return xpath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new XPath object using the default prefix for the android namespace.
|
||||
* @see #DEFAULT_NS_PREFIX
|
||||
*/
|
||||
public static XPath newXPath() {
|
||||
return newXPath(DEFAULT_NS_PREFIX);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.common.project;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.common.project.XmlErrorHandler.XmlErrorListener;
|
||||
|
||||
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.IWorkspaceRoot;
|
||||
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.jdt.core.Flags;
|
||||
import org.eclipse.jdt.core.IClasspathEntry;
|
||||
import org.eclipse.jdt.core.IJavaModel;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jdt.core.IMethod;
|
||||
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.jdt.ui.JavaUI;
|
||||
import org.eclipse.jdt.ui.actions.OpenJavaPerspectiveAction;
|
||||
import org.eclipse.jface.text.BadLocationException;
|
||||
import org.eclipse.jface.text.IDocument;
|
||||
import org.eclipse.jface.text.IRegion;
|
||||
import org.eclipse.ui.IEditorInput;
|
||||
import org.eclipse.ui.IEditorPart;
|
||||
import org.eclipse.ui.IWorkbench;
|
||||
import org.eclipse.ui.IWorkbenchPage;
|
||||
import org.eclipse.ui.IWorkbenchWindow;
|
||||
import org.eclipse.ui.PartInitException;
|
||||
import org.eclipse.ui.PlatformUI;
|
||||
import org.eclipse.ui.texteditor.IDocumentProvider;
|
||||
import org.eclipse.ui.texteditor.ITextEditor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Utility methods to manipulate projects.
|
||||
*/
|
||||
public final class BaseProjectHelper {
|
||||
|
||||
public static final String TEST_CLASS_OK = null;
|
||||
|
||||
/**
|
||||
* returns a list of source classpath for a specified project
|
||||
* @param javaProject
|
||||
* @return a list of path relative to the workspace root.
|
||||
*/
|
||||
public static ArrayList<IPath> getSourceClasspaths(IJavaProject javaProject) {
|
||||
ArrayList<IPath> sourceList = new ArrayList<IPath>();
|
||||
IClasspathEntry[] classpaths = javaProject.readRawClasspath();
|
||||
if (classpaths != null) {
|
||||
for (IClasspathEntry e : classpaths) {
|
||||
if (e.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
|
||||
sourceList.add(e.getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
return sourceList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a marker to a file on a specific line
|
||||
* @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 lineNumber the line number where to put the mark
|
||||
* @param severity the severity of the marker.
|
||||
* @return the IMarker that was added or null if it failed to add one.
|
||||
*/
|
||||
public final static IMarker addMarker(IResource file, String markerId,
|
||||
String message, int lineNumber, int severity) {
|
||||
try {
|
||||
IMarker marker = file.createMarker(markerId);
|
||||
marker.setAttribute(IMarker.MESSAGE, message);
|
||||
marker.setAttribute(IMarker.SEVERITY, severity);
|
||||
if (lineNumber == -1) {
|
||||
lineNumber = 1;
|
||||
}
|
||||
marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
|
||||
|
||||
// on Windows, when adding a marker to a project, it takes a refresh for the marker
|
||||
// to show. In order to fix this we're forcing a refresh of elements receiving
|
||||
// markers (and only the element, not its children), to force the marker display.
|
||||
file.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor());
|
||||
|
||||
return marker;
|
||||
} catch (CoreException e) {
|
||||
AdtPlugin.log(e, "Failed to add marker '%1$s' to '%2$s'",
|
||||
markerId, file.getFullPath());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a marker to a resource.
|
||||
* @param resource the file to be marked
|
||||
* @param markerId The id of the marker to add.
|
||||
* @param message the message associated with the mark
|
||||
* @param severity the severity of the marker.
|
||||
* @return the IMarker that was added or null if it failed to add one.
|
||||
*/
|
||||
public final static IMarker addMarker(IResource resource, String markerId,
|
||||
String message, int severity) {
|
||||
try {
|
||||
IMarker marker = resource.createMarker(markerId);
|
||||
marker.setAttribute(IMarker.MESSAGE, message);
|
||||
marker.setAttribute(IMarker.SEVERITY, severity);
|
||||
|
||||
// on Windows, when adding a marker to a project, it takes a refresh for the marker
|
||||
// to show. In order to fix this we're forcing a refresh of elements receiving
|
||||
// markers (and only the element, not its children), to force the marker display.
|
||||
resource.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor());
|
||||
|
||||
return marker;
|
||||
} catch (CoreException e) {
|
||||
AdtPlugin.log(e, "Failed to add marker '%1$s' to '%2$s'",
|
||||
markerId, resource.getFullPath());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a class name is valid for usage in the manifest.
|
||||
* <p/>
|
||||
* This tests the class existence, that it can be instantiated (ie it must not be abstract,
|
||||
* nor non static if enclosed), and that it extends the proper super class (not necessarily
|
||||
* directly)
|
||||
* @param javaProject the {@link IJavaProject} containing the class.
|
||||
* @param className the fully qualified name of the class to test.
|
||||
* @param superClassName the fully qualified name of the expected super class.
|
||||
* @param testVisibility if <code>true</code>, the method will check the visibility of the class
|
||||
* or of its constructors.
|
||||
* @return {@link #TEST_CLASS_OK} or an error message.
|
||||
*/
|
||||
public final static String testClassForManifest(IJavaProject javaProject, String className,
|
||||
String superClassName, boolean testVisibility) {
|
||||
try {
|
||||
// replace $ by .
|
||||
String javaClassName = className.replaceAll("\\$", "\\."); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
|
||||
// look for the IType object for this class
|
||||
IType type = javaProject.findType(javaClassName);
|
||||
if (type != null && type.exists()) {
|
||||
// test that the class is not abstract
|
||||
int flags = type.getFlags();
|
||||
if (Flags.isAbstract(flags)) {
|
||||
return String.format("%1$s is abstract", className);
|
||||
}
|
||||
|
||||
// test whether the class is public or not.
|
||||
if (testVisibility && Flags.isPublic(flags) == false) {
|
||||
// if its not public, it may have a public default constructor,
|
||||
// which would then be fine.
|
||||
IMethod basicConstructor = type.getMethod(type.getElementName(), new String[0]);
|
||||
if (basicConstructor != null && basicConstructor.exists()) {
|
||||
int constructFlags = basicConstructor.getFlags();
|
||||
if (Flags.isPublic(constructFlags) == false) {
|
||||
return String.format(
|
||||
"%1$s or its default constructor must be public for the system to be able to instantiate it",
|
||||
className);
|
||||
}
|
||||
} else {
|
||||
return String.format(
|
||||
"%1$s must be public, or the system will not be able to instantiate it.",
|
||||
className);
|
||||
}
|
||||
}
|
||||
|
||||
// If it's enclosed, test that it's static. If its declaring class is enclosed
|
||||
// as well, test that it is also static, and public.
|
||||
IType declaringType = type;
|
||||
do {
|
||||
IType tmpType = declaringType.getDeclaringType();
|
||||
if (tmpType != null) {
|
||||
if (tmpType.exists()) {
|
||||
flags = declaringType.getFlags();
|
||||
if (Flags.isStatic(flags) == false) {
|
||||
return String.format("%1$s is enclosed, but not static",
|
||||
declaringType.getFullyQualifiedName());
|
||||
}
|
||||
|
||||
flags = tmpType.getFlags();
|
||||
if (testVisibility && Flags.isPublic(flags) == false) {
|
||||
return String.format("%1$s is not public",
|
||||
tmpType.getFullyQualifiedName());
|
||||
}
|
||||
} else {
|
||||
// if it doesn't exist, we need to exit so we may as well mark it null.
|
||||
tmpType = null;
|
||||
}
|
||||
}
|
||||
declaringType = tmpType;
|
||||
} while (declaringType != null);
|
||||
|
||||
// test the class inherit from the specified super class.
|
||||
// get the type hierarchy
|
||||
ITypeHierarchy hierarchy = type.newSupertypeHierarchy(new NullProgressMonitor());
|
||||
|
||||
// if the super class is not the reference class, it may inherit from
|
||||
// it so we get its supertype. At some point it will be null and we
|
||||
// will stop
|
||||
IType superType = type;
|
||||
boolean foundProperSuperClass = false;
|
||||
while ((superType = hierarchy.getSuperclass(superType)) != null &&
|
||||
superType.exists()) {
|
||||
if (superClassName.equals(superType.getFullyQualifiedName())) {
|
||||
foundProperSuperClass = true;
|
||||
}
|
||||
}
|
||||
|
||||
// didn't find the proper superclass? return false.
|
||||
if (foundProperSuperClass == false) {
|
||||
return String.format("%1$s does not extend %2$s", className, superClassName);
|
||||
}
|
||||
|
||||
return TEST_CLASS_OK;
|
||||
} else {
|
||||
return String.format("Class %1$s does not exist", className);
|
||||
}
|
||||
} catch (JavaModelException e) {
|
||||
return String.format("%1$s: %2$s", className, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the manifest file for errors.
|
||||
* <p/>
|
||||
* This starts by removing the current XML marker, and then parses the xml for errors, both
|
||||
* of XML type and of Android type (checking validity of class files).
|
||||
* @param manifestFile
|
||||
* @param errorListener
|
||||
* @throws CoreException
|
||||
*/
|
||||
public static AndroidManifestParser parseManifestForError(IFile manifestFile,
|
||||
XmlErrorListener errorListener) throws CoreException {
|
||||
// remove previous markers
|
||||
if (manifestFile.exists()) {
|
||||
manifestFile.deleteMarkers(AndroidConstants.MARKER_XML, true, IResource.DEPTH_ZERO);
|
||||
manifestFile.deleteMarkers(AndroidConstants.MARKER_ANDROID, true, IResource.DEPTH_ZERO);
|
||||
}
|
||||
|
||||
// and parse
|
||||
return AndroidManifestParser.parseForError(
|
||||
BaseProjectHelper.getJavaProject(manifestFile.getProject()),
|
||||
manifestFile, errorListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link IJavaProject} for a {@link IProject} object.
|
||||
* <p/>
|
||||
* This checks if the project has the Java Nature first.
|
||||
* @param project
|
||||
* @return the IJavaProject or null if the project couldn't be created or if the project
|
||||
* does not have the Java Nature.
|
||||
* @throws CoreException
|
||||
*/
|
||||
public static IJavaProject getJavaProject(IProject project) throws CoreException {
|
||||
if (project != null && project.hasNature(JavaCore.NATURE_ID)) {
|
||||
return JavaCore.create(project);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reveals a specific line in the source file defining a specified class,
|
||||
* for a specific project.
|
||||
* @param project
|
||||
* @param className
|
||||
* @param line
|
||||
*/
|
||||
public static void revealSource(IProject project, String className, int line) {
|
||||
// in case the type is enclosed, we need to replace the $ with .
|
||||
className = className.replaceAll("\\$", "\\."); //$NON-NLS-1$ //$NON-NLS2$
|
||||
|
||||
// get the java project
|
||||
IJavaProject javaProject = JavaCore.create(project);
|
||||
|
||||
try {
|
||||
// look for the IType matching the class name.
|
||||
IType result = javaProject.findType(className);
|
||||
if (result != null && result.exists()) {
|
||||
// before we show the type in an editor window, we make sure the current
|
||||
// workbench page has an editor area (typically the ddms perspective doesn't).
|
||||
IWorkbench workbench = PlatformUI.getWorkbench();
|
||||
IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
|
||||
IWorkbenchPage page = window.getActivePage();
|
||||
if (page.isEditorAreaVisible() == false) {
|
||||
// no editor area? we open the java perspective.
|
||||
new OpenJavaPerspectiveAction().run();
|
||||
}
|
||||
|
||||
IEditorPart editor = JavaUI.openInEditor(result);
|
||||
if (editor instanceof ITextEditor) {
|
||||
// get the text editor that was just opened.
|
||||
ITextEditor textEditor = (ITextEditor)editor;
|
||||
|
||||
IEditorInput input = textEditor.getEditorInput();
|
||||
|
||||
// get the location of the line to show.
|
||||
IDocumentProvider documentProvider = textEditor.getDocumentProvider();
|
||||
IDocument document = documentProvider.getDocument(input);
|
||||
IRegion lineInfo = document.getLineInformation(line - 1);
|
||||
|
||||
// select and reveal the line.
|
||||
textEditor.selectAndReveal(lineInfo.getOffset(), lineInfo.getLength());
|
||||
}
|
||||
}
|
||||
} catch (JavaModelException e) {
|
||||
} catch (PartInitException e) {
|
||||
} catch (BadLocationException e) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of android-flagged projects. This list contains projects that are opened
|
||||
* in the workspace and that are flagged as android project (through the android nature)
|
||||
* @return an array of IJavaProject, which can be empty if no projects match.
|
||||
*/
|
||||
public static IJavaProject[] getAndroidProjects() {
|
||||
IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
|
||||
IJavaModel javaModel = JavaCore.create(workspaceRoot);
|
||||
|
||||
return getAndroidProjects(javaModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of android-flagged projects for the specified java Model.
|
||||
* This list contains projects that are opened in the workspace and that are flagged as android
|
||||
* project (through the android nature)
|
||||
* @param javaModel the Java Model object corresponding for the current workspace root.
|
||||
* @return an array of IJavaProject, which can be empty if no projects match.
|
||||
*/
|
||||
public static IJavaProject[] getAndroidProjects(IJavaModel javaModel) {
|
||||
// get the java projects
|
||||
IJavaProject[] javaProjectList = null;
|
||||
try {
|
||||
javaProjectList = javaModel.getJavaProjects();
|
||||
}
|
||||
catch (JavaModelException jme) {
|
||||
return new IJavaProject[0];
|
||||
}
|
||||
|
||||
// temp list to build the android project array
|
||||
ArrayList<IJavaProject> androidProjectList = new ArrayList<IJavaProject>();
|
||||
|
||||
// loop through the projects and add the android flagged projects to the temp list.
|
||||
for (IJavaProject javaProject : javaProjectList) {
|
||||
// get the workspace project object
|
||||
IProject project = javaProject.getProject();
|
||||
|
||||
// check if it's an android project based on its nature
|
||||
try {
|
||||
if (project.hasNature(AndroidConstants.NATURE)) {
|
||||
androidProjectList.add(javaProject);
|
||||
}
|
||||
} catch (CoreException e) {
|
||||
// this exception, thrown by IProject.hasNature(), means the project either doesn't
|
||||
// exist or isn't opened. So, in any case we just skip it (the exception will
|
||||
// bypass the ArrayList.add()
|
||||
}
|
||||
}
|
||||
|
||||
// return the android projects list.
|
||||
return androidProjectList.toArray(new IJavaProject[androidProjectList.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link IFolder} representing the output for the project.
|
||||
* <p>
|
||||
* The project must be a java project and be opened, or the method will return null.
|
||||
* @param project the {@link IProject}
|
||||
* @return an IFolder item or null.
|
||||
*/
|
||||
public final static IFolder getOutputFolder(IProject project) {
|
||||
try {
|
||||
if (project.isOpen() && project.hasNature(JavaCore.NATURE_ID)) {
|
||||
// get a java project from the normal project object
|
||||
IJavaProject javaProject = JavaCore.create(project);
|
||||
|
||||
IPath path = javaProject.getOutputLocation();
|
||||
IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
|
||||
IResource outputResource = wsRoot.findMember(path);
|
||||
if (outputResource != null && outputResource.getType() == IResource.FOLDER) {
|
||||
return (IFolder)outputResource;
|
||||
}
|
||||
}
|
||||
} catch (JavaModelException e) {
|
||||
// Let's do nothing and return null
|
||||
} catch (CoreException e) {
|
||||
// Let's do nothing and return null
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.common.project;
|
||||
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
|
||||
import org.eclipse.core.resources.IFolder;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.jface.dialogs.MessageDialog;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
import org.eclipse.swt.widgets.FileDialog;
|
||||
import org.eclipse.swt.widgets.Shell;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
/**
|
||||
* Export helper for project.
|
||||
*/
|
||||
public final class ExportHelper {
|
||||
|
||||
private static IExportCallback sCallback;
|
||||
|
||||
public interface IExportCallback {
|
||||
void startExportWizard(IProject project);
|
||||
}
|
||||
|
||||
public static void setCallback(IExportCallback callback) {
|
||||
sCallback = callback;
|
||||
}
|
||||
|
||||
public static void startExportWizard(IProject project) {
|
||||
if (sCallback != null) {
|
||||
sCallback.startExportWizard(project);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports an <b>unsigned</b> version of the application created by the given project.
|
||||
* @param project the project to export
|
||||
*/
|
||||
public static void exportProject(IProject project) {
|
||||
Shell shell = Display.getCurrent().getActiveShell();
|
||||
|
||||
// get the java project to get the output directory
|
||||
IFolder outputFolder = BaseProjectHelper.getOutputFolder(project);
|
||||
if (outputFolder != null) {
|
||||
IPath binLocation = outputFolder.getLocation();
|
||||
|
||||
// make the full path to the package
|
||||
String fileName = project.getName() + AndroidConstants.DOT_ANDROID_PACKAGE;
|
||||
|
||||
File file = new File(binLocation.toOSString() + File.separator + fileName);
|
||||
|
||||
if (file.exists() == false || file.isFile() == false) {
|
||||
MessageDialog.openInformation(Display.getCurrent().getActiveShell(),
|
||||
"Android IDE Plug-in",
|
||||
String.format("Failed to export %1$s: %2$s doesn't exist!",
|
||||
project.getName(), file.getPath()));
|
||||
return;
|
||||
}
|
||||
|
||||
// ok now pop up the file save window
|
||||
FileDialog fileDialog = new FileDialog(shell, SWT.SAVE);
|
||||
|
||||
fileDialog.setText("Export Project");
|
||||
fileDialog.setFileName(fileName);
|
||||
|
||||
String saveLocation = fileDialog.open();
|
||||
if (saveLocation != null) {
|
||||
// get the stream from the original file
|
||||
|
||||
ZipInputStream zis = null;
|
||||
ZipOutputStream zos = null;
|
||||
FileInputStream input = null;
|
||||
FileOutputStream output = null;
|
||||
|
||||
try {
|
||||
input = new FileInputStream(file);
|
||||
zis = new ZipInputStream(input);
|
||||
|
||||
// get an output stream into the new file
|
||||
File saveFile = new File(saveLocation);
|
||||
output = new FileOutputStream(saveFile);
|
||||
zos = new ZipOutputStream(output);
|
||||
} catch (FileNotFoundException e) {
|
||||
// only the input/output stream are throwing this exception.
|
||||
// so we only have to close zis if output is the one that threw.
|
||||
if (zis != null) {
|
||||
try {
|
||||
zis.close();
|
||||
} catch (IOException e1) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
MessageDialog.openInformation(shell, "Android IDE Plug-in",
|
||||
String.format("Failed to export %1$s: %2$s doesn't exist!",
|
||||
project.getName(), file.getPath()));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ZipEntry entry;
|
||||
|
||||
byte[] buffer = new byte[4096];
|
||||
|
||||
while ((entry = zis.getNextEntry()) != null) {
|
||||
String name = entry.getName();
|
||||
|
||||
// do not take directories or anything inside the META-INF folder since
|
||||
// we want to strip the signature.
|
||||
if (entry.isDirectory() || name.startsWith("META-INF/")) { //$NON-NL1$
|
||||
continue;
|
||||
}
|
||||
|
||||
ZipEntry newEntry;
|
||||
|
||||
// Preserve the STORED method of the input entry.
|
||||
if (entry.getMethod() == JarEntry.STORED) {
|
||||
newEntry = new JarEntry(entry);
|
||||
} else {
|
||||
// Create a new entry so that the compressed len is recomputed.
|
||||
newEntry = new JarEntry(name);
|
||||
}
|
||||
|
||||
// add the entry to the jar archive
|
||||
zos.putNextEntry(newEntry);
|
||||
|
||||
// read the content of the entry from the input stream, and write it into the archive.
|
||||
int count;
|
||||
while ((count = zis.read(buffer)) != -1) {
|
||||
zos.write(buffer, 0, count);
|
||||
}
|
||||
|
||||
// close the entry for this file
|
||||
zos.closeEntry();
|
||||
zis.closeEntry();
|
||||
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
MessageDialog.openInformation(shell, "Android IDE Plug-in",
|
||||
String.format("Failed to export %1$s: %2$s",
|
||||
project.getName(), e.getMessage()));
|
||||
} finally {
|
||||
try {
|
||||
zos.close();
|
||||
} catch (IOException e) {
|
||||
// pass
|
||||
}
|
||||
try {
|
||||
zis.close();
|
||||
} catch (IOException e) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
MessageDialog.openInformation(shell, "Android IDE Plug-in",
|
||||
String.format("Failed to export %1$s: Could not get project output location",
|
||||
project.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.common.project;
|
||||
|
||||
import com.android.ide.eclipse.common.project.BaseProjectHelper;
|
||||
|
||||
import org.eclipse.core.resources.IWorkspaceRoot;
|
||||
import org.eclipse.core.resources.ResourcesPlugin;
|
||||
import org.eclipse.jdt.core.IJavaModel;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jdt.core.JavaCore;
|
||||
import org.eclipse.jdt.ui.JavaElementLabelProvider;
|
||||
import org.eclipse.jface.viewers.ILabelProvider;
|
||||
import org.eclipse.jface.window.Window;
|
||||
import org.eclipse.swt.widgets.Shell;
|
||||
import org.eclipse.ui.dialogs.ElementListSelectionDialog;
|
||||
|
||||
/**
|
||||
* Helper class to deal with displaying a project choosing dialog that lists only the
|
||||
* projects with the Android nature.
|
||||
*/
|
||||
public class ProjectChooserHelper {
|
||||
|
||||
private final Shell mParentShell;
|
||||
|
||||
/**
|
||||
* List of current android projects. Since the dialog is modal, we'll just get
|
||||
* the list once on-demand.
|
||||
*/
|
||||
private IJavaProject[] mAndroidProjects;
|
||||
|
||||
public ProjectChooserHelper(Shell parentShell) {
|
||||
mParentShell = parentShell;
|
||||
}
|
||||
/**
|
||||
* Displays a project chooser dialog which lists all available projects with the Android nature.
|
||||
* <p/>
|
||||
* The list of project is built from Android flagged projects currently opened in the workspace.
|
||||
*
|
||||
* @param projectName If non null and not empty, represents the name of an Android project
|
||||
* that will be selected by default.
|
||||
* @return the project chosen by the user in the dialog, or null if the dialog was canceled.
|
||||
*/
|
||||
public IJavaProject chooseJavaProject(String projectName) {
|
||||
ILabelProvider labelProvider = new JavaElementLabelProvider(
|
||||
JavaElementLabelProvider.SHOW_DEFAULT);
|
||||
ElementListSelectionDialog dialog = new ElementListSelectionDialog(
|
||||
mParentShell, labelProvider);
|
||||
dialog.setTitle("Project Selection");
|
||||
dialog.setMessage("Select a project to constrain your search.");
|
||||
|
||||
IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
|
||||
IJavaModel javaModel = JavaCore.create(workspaceRoot);
|
||||
|
||||
// set the elements in the dialog. These are opened android projects.
|
||||
dialog.setElements(getAndroidProjects(javaModel));
|
||||
|
||||
// look for the project matching the given project name
|
||||
IJavaProject javaProject = null;
|
||||
if (projectName != null && projectName.length() > 0) {
|
||||
javaProject = javaModel.getJavaProject(projectName);
|
||||
}
|
||||
|
||||
// if we found it, we set the initial selection in the dialog to this one.
|
||||
if (javaProject != null) {
|
||||
dialog.setInitialSelections(new Object[] { javaProject });
|
||||
}
|
||||
|
||||
// open the dialog and return the object selected if OK was clicked, or null otherwise
|
||||
if (dialog.open() == Window.OK) {
|
||||
return (IJavaProject)dialog.getFirstResult();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of Android projects.
|
||||
* <p/>
|
||||
* Because this list can be time consuming, this class caches the list of project.
|
||||
* It is recommended to call this method instead of
|
||||
* {@link BaseProjectHelper#getAndroidProjects()}.
|
||||
*
|
||||
* @param javaModel the java model. Can be null.
|
||||
*/
|
||||
public IJavaProject[] getAndroidProjects(IJavaModel javaModel) {
|
||||
if (mAndroidProjects == null) {
|
||||
if (javaModel == null) {
|
||||
mAndroidProjects = BaseProjectHelper.getAndroidProjects();
|
||||
} else {
|
||||
mAndroidProjects = BaseProjectHelper.getAndroidProjects(javaModel);
|
||||
}
|
||||
}
|
||||
|
||||
return mAndroidProjects;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.common.project;
|
||||
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.resources.IMarker;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.SAXParseException;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
/**
|
||||
* XML error handler used by the parser to report errors/warnings.
|
||||
*/
|
||||
public class XmlErrorHandler extends DefaultHandler {
|
||||
|
||||
/** file being parsed */
|
||||
private IFile mFile;
|
||||
|
||||
/** link to the delta visitor, to set the xml error flag */
|
||||
private XmlErrorListener mErrorListener;
|
||||
|
||||
/**
|
||||
* Classes which implement this interface provide a method that deals
|
||||
* with XML errors.
|
||||
*/
|
||||
public interface XmlErrorListener {
|
||||
/**
|
||||
* Sent when an XML error is detected.
|
||||
*/
|
||||
public void errorFound();
|
||||
}
|
||||
|
||||
public static class BasicXmlErrorListener implements XmlErrorListener {
|
||||
public boolean mHasXmlError = false;
|
||||
|
||||
public void errorFound() {
|
||||
mHasXmlError = true;
|
||||
}
|
||||
}
|
||||
|
||||
public XmlErrorHandler(IFile file, XmlErrorListener errorListener) {
|
||||
mFile = file;
|
||||
mErrorListener = errorListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Xml Error call back
|
||||
* @param exception the parsing exception
|
||||
*/
|
||||
@Override
|
||||
public void error(SAXParseException exception) throws SAXException {
|
||||
handleError(exception, exception.getLineNumber());
|
||||
}
|
||||
|
||||
/**
|
||||
* Xml Fatal Error call back
|
||||
* @param exception the parsing exception
|
||||
*/
|
||||
@Override
|
||||
public void fatalError(SAXParseException exception) throws SAXException {
|
||||
handleError(exception, exception.getLineNumber());
|
||||
}
|
||||
|
||||
/**
|
||||
* Xml Warning call back
|
||||
* @param exception the parsing exception
|
||||
*/
|
||||
@Override
|
||||
public void warning(SAXParseException exception) throws SAXException {
|
||||
BaseProjectHelper.addMarker(mFile, AndroidConstants.MARKER_XML, exception.getMessage(),
|
||||
exception.getLineNumber(), IMarker.SEVERITY_WARNING);
|
||||
}
|
||||
|
||||
protected final IFile getFile() {
|
||||
return mFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a parsing error and an optional line number.
|
||||
* @param exception
|
||||
* @param lineNumber
|
||||
*/
|
||||
protected void handleError(Exception exception, int lineNumber) {
|
||||
if (mErrorListener != null) {
|
||||
mErrorListener.errorFound();
|
||||
}
|
||||
|
||||
if (lineNumber != -1) {
|
||||
BaseProjectHelper.addMarker(mFile, AndroidConstants.MARKER_XML, exception.getMessage(),
|
||||
lineNumber, IMarker.SEVERITY_ERROR);
|
||||
} else {
|
||||
BaseProjectHelper.addMarker(mFile, AndroidConstants.MARKER_XML, exception.getMessage(),
|
||||
IMarker.SEVERITY_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.common.resources;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo;
|
||||
import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo.Format;
|
||||
import com.android.ide.eclipse.common.resources.ViewClassInfo.LayoutParamsInfo;
|
||||
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
/**
|
||||
* Parser for attributes description files.
|
||||
*/
|
||||
public final class AttrsXmlParser {
|
||||
|
||||
private Document mDocument;
|
||||
private String mOsAttrsXmlPath;
|
||||
// all attributes that have the same name are supposed to have the same
|
||||
// parameters so we'll keep a cache of them to avoid processing them twice.
|
||||
private HashMap<String, AttributeInfo> mAttributeMap;
|
||||
|
||||
/** Map of all attribute names for a given element */
|
||||
private HashMap<String, DeclareStyleableInfo> mStyleMap;
|
||||
|
||||
/** Map of all (constant, value) pairs for attributes of format enum or flag.
|
||||
* E.g. for attribute name=gravity, this tells us there's an enum/flag called "center"
|
||||
* with value 0x11.
|
||||
*/
|
||||
private Map<String, Map<String, Integer>> mEnumFlagValues;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link AttrsXmlParser}, set to load things from the given
|
||||
* XML file. Nothing has been parsed yet. Callers should call {@link #preload()}
|
||||
* next.
|
||||
*/
|
||||
public AttrsXmlParser(String osAttrsXmlPath) {
|
||||
this(osAttrsXmlPath, null /* inheritableAttributes */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link AttrsXmlParser} set to load things from the given
|
||||
* XML file. If inheritableAttributes is non-null, it must point to a preloaded
|
||||
* {@link AttrsXmlParser} which attributes will be used for this one. Since
|
||||
* already defined attributes are not modifiable, they are thus "inherited".
|
||||
*/
|
||||
public AttrsXmlParser(String osAttrsXmlPath, AttrsXmlParser inheritableAttributes) {
|
||||
mOsAttrsXmlPath = osAttrsXmlPath;
|
||||
|
||||
// styles are not inheritable.
|
||||
mStyleMap = new HashMap<String, DeclareStyleableInfo>();
|
||||
|
||||
if (inheritableAttributes == null) {
|
||||
mAttributeMap = new HashMap<String, AttributeInfo>();
|
||||
mEnumFlagValues = new HashMap<String, Map<String,Integer>>();
|
||||
} else {
|
||||
mAttributeMap = new HashMap<String, AttributeInfo>(inheritableAttributes.mAttributeMap);
|
||||
mEnumFlagValues = new HashMap<String, Map<String,Integer>>(
|
||||
inheritableAttributes.mEnumFlagValues);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The OS path of the attrs.xml file parsed
|
||||
*/
|
||||
public String getOsAttrsXmlPath() {
|
||||
return mOsAttrsXmlPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preloads the document, parsing all attributes and declared styles.
|
||||
*
|
||||
* @return Self, for command chaining.
|
||||
*/
|
||||
public AttrsXmlParser preload() {
|
||||
Document doc = getDocument();
|
||||
|
||||
if (doc == null) {
|
||||
AdtPlugin.log(IStatus.WARNING, "Failed to find %1$s", //$NON-NLS-1$
|
||||
mOsAttrsXmlPath);
|
||||
return this;
|
||||
}
|
||||
|
||||
Node res = doc.getFirstChild();
|
||||
while (res != null &&
|
||||
res.getNodeType() != Node.ELEMENT_NODE &&
|
||||
!res.getNodeName().equals("resources")) { //$NON-NLS-1$
|
||||
res = res.getNextSibling();
|
||||
}
|
||||
|
||||
if (res == null) {
|
||||
AdtPlugin.log(IStatus.WARNING, "Failed to find a <resources> node in %1$s", //$NON-NLS-1$
|
||||
mOsAttrsXmlPath);
|
||||
return this;
|
||||
}
|
||||
|
||||
parseResources(res);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all attributes & javadoc for the view class info based on the class name.
|
||||
*/
|
||||
public void loadViewAttributes(ViewClassInfo info) {
|
||||
if (getDocument() != null) {
|
||||
String xmlName = info.getShortClassName();
|
||||
DeclareStyleableInfo style = mStyleMap.get(xmlName);
|
||||
if (style != null) {
|
||||
info.setAttributes(style.getAttributes());
|
||||
info.setJavaDoc(style.getJavaDoc());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all attributes for the layout data info based on the class name.
|
||||
*/
|
||||
public void loadLayoutParamsAttributes(LayoutParamsInfo info) {
|
||||
if (getDocument() != null) {
|
||||
// Transforms "LinearLayout" and "LayoutParams" into "LinearLayout_Layout".
|
||||
String xmlName = String.format("%1$s_%2$s", //$NON-NLS-1$
|
||||
info.getViewLayoutClass().getShortClassName(),
|
||||
info.getShortClassName());
|
||||
xmlName = xmlName.replaceFirst("Params$", ""); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
|
||||
DeclareStyleableInfo style = mStyleMap.get(xmlName);
|
||||
if (style != null) {
|
||||
info.setAttributes(style.getAttributes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all decleare-styleable found in the xml file.
|
||||
*/
|
||||
public Map<String, DeclareStyleableInfo> getDeclareStyleableList() {
|
||||
return Collections.unmodifiableMap(mStyleMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of all enum and flag constants sorted by parent attribute name.
|
||||
* The map is attribute_name => (constant_name => integer_value).
|
||||
*/
|
||||
public Map<String, Map<String, Integer>> getEnumFlagValues() {
|
||||
return mEnumFlagValues;
|
||||
}
|
||||
|
||||
//-------------------------
|
||||
|
||||
/**
|
||||
* Creates an XML document from the attrs.xml OS path.
|
||||
* May return null if the file doesn't exist or cannot be parsed.
|
||||
*/
|
||||
private Document getDocument() {
|
||||
if (mDocument == null) {
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setIgnoringComments(false);
|
||||
try {
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
mDocument = builder.parse(new File(mOsAttrsXmlPath));
|
||||
} catch (ParserConfigurationException e) {
|
||||
AdtPlugin.log(e, "Failed to create XML document builder for %1$s", //$NON-NLS-1$
|
||||
mOsAttrsXmlPath);
|
||||
} catch (SAXException e) {
|
||||
AdtPlugin.log(e, "Failed to parse XML document %1$s", //$NON-NLS-1$
|
||||
mOsAttrsXmlPath);
|
||||
} catch (IOException e) {
|
||||
AdtPlugin.log(e, "Failed to read XML document %1$s", //$NON-NLS-1$
|
||||
mOsAttrsXmlPath);
|
||||
}
|
||||
}
|
||||
return mDocument;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all the <declare-styleable> and <attr> nodes in the top <resources> node.
|
||||
*/
|
||||
private void parseResources(Node res) {
|
||||
Node lastComment = null;
|
||||
for (Node node = res.getFirstChild(); node != null; node = node.getNextSibling()) {
|
||||
switch (node.getNodeType()) {
|
||||
case Node.COMMENT_NODE:
|
||||
lastComment = node;
|
||||
break;
|
||||
case Node.ELEMENT_NODE:
|
||||
if (node.getNodeName().equals("declare-styleable")) { //$NON-NLS-1$
|
||||
Node nameNode = node.getAttributes().getNamedItem("name"); //$NON-NLS-1$
|
||||
if (nameNode != null) {
|
||||
String name = nameNode.getNodeValue();
|
||||
|
||||
Node parentNode = node.getAttributes().getNamedItem("parent"); //$NON-NLS-1$
|
||||
String parents = parentNode == null ? null : parentNode.getNodeValue();
|
||||
|
||||
if (name != null && !mStyleMap.containsKey(name)) {
|
||||
DeclareStyleableInfo style = parseDeclaredStyleable(name, node);
|
||||
if (parents != null) {
|
||||
style.setParents(parents.split("[ ,|]")); //$NON-NLS-1$
|
||||
}
|
||||
mStyleMap.put(name, style);
|
||||
if (lastComment != null) {
|
||||
style.setJavaDoc(formatJavadoc(lastComment.getNodeValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (node.getNodeName().equals("attr")) { //$NON-NLS-1$
|
||||
parseAttr(node, lastComment);
|
||||
}
|
||||
lastComment = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an <attr> node and convert it into an {@link AttributeInfo} if it is valid.
|
||||
*/
|
||||
private AttributeInfo parseAttr(Node attrNode, Node lastComment) {
|
||||
AttributeInfo info = null;
|
||||
Node nameNode = attrNode.getAttributes().getNamedItem("name"); //$NON-NLS-1$
|
||||
if (nameNode != null) {
|
||||
String name = nameNode.getNodeValue();
|
||||
if (name != null) {
|
||||
info = mAttributeMap.get(name);
|
||||
// If the attribute is unknown yet, parse it.
|
||||
// If the attribute is know but its format is unknown, parse it too.
|
||||
if (info == null || info.getFormats().length == 0) {
|
||||
info = parseAttributeTypes(attrNode, name);
|
||||
if (info != null) {
|
||||
mAttributeMap.put(name, info);
|
||||
}
|
||||
} else if (lastComment != null) {
|
||||
info = new AttributeInfo(info);
|
||||
}
|
||||
if (info != null) {
|
||||
if (lastComment != null) {
|
||||
info.setJavaDoc(formatJavadoc(lastComment.getNodeValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all the attributes for a particular style node,
|
||||
* e.g. a declare-styleable of name "TextView" or "LinearLayout_Layout".
|
||||
*
|
||||
* @param styleName The name of the declare-styleable node
|
||||
* @param declareStyleableNode The declare-styleable node itself
|
||||
*/
|
||||
private DeclareStyleableInfo parseDeclaredStyleable(String styleName,
|
||||
Node declareStyleableNode) {
|
||||
ArrayList<AttributeInfo> attrs = new ArrayList<AttributeInfo>();
|
||||
Node lastComment = null;
|
||||
for (Node node = declareStyleableNode.getFirstChild();
|
||||
node != null;
|
||||
node = node.getNextSibling()) {
|
||||
|
||||
switch (node.getNodeType()) {
|
||||
case Node.COMMENT_NODE:
|
||||
lastComment = node;
|
||||
break;
|
||||
case Node.ELEMENT_NODE:
|
||||
if (node.getNodeName().equals("attr")) { //$NON-NLS-1$
|
||||
AttributeInfo info = parseAttr(node, lastComment);
|
||||
if (info != null) {
|
||||
attrs.add(info);
|
||||
}
|
||||
}
|
||||
lastComment = null;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new DeclareStyleableInfo(styleName, attrs.toArray(new AttributeInfo[attrs.size()]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link AttributeInfo} for a specific <attr> XML node.
|
||||
* This gets the javadoc, the type, the name and the enum/flag values if any.
|
||||
* <p/>
|
||||
* The XML node is expected to have the following attributes:
|
||||
* <ul>
|
||||
* <li>"name", which is mandatory. The node is skipped if this is missing.</li>
|
||||
* <li>"format".</li>
|
||||
* </ul>
|
||||
* The format may be one type or two types (e.g. "reference|color").
|
||||
* An extra format can be implied: "enum" or "flag" are not specified in the "format" attribute,
|
||||
* they are implicitely stated by the presence of sub-nodes <enum> or <flag>.
|
||||
* <p/>
|
||||
* By design, <attr> nodes of the same name MUST have the same type.
|
||||
* Attribute nodes are thus cached by name and reused as much as possible.
|
||||
* When reusing a node, it is duplicated and its javadoc reassigned.
|
||||
*/
|
||||
private AttributeInfo parseAttributeTypes(Node attrNode, String name) {
|
||||
TreeSet<AttributeInfo.Format> formats = new TreeSet<AttributeInfo.Format>();
|
||||
String[] enumValues = null;
|
||||
String[] flagValues = null;
|
||||
|
||||
Node attrFormat = attrNode.getAttributes().getNamedItem("format"); //$NON-NLS-1$
|
||||
if (attrFormat != null) {
|
||||
for (String f : attrFormat.getNodeValue().split("\\|")) { //$NON-NLS-1$
|
||||
try {
|
||||
Format format = AttributeInfo.Format.valueOf(f.toUpperCase());
|
||||
// enum and flags are handled differently right below
|
||||
if (format != null &&
|
||||
format != AttributeInfo.Format.ENUM &&
|
||||
format != AttributeInfo.Format.FLAG) {
|
||||
formats.add(format);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
AdtPlugin.log(e, "Unknown format name '%s' in <attr name=\"%s\">, file '%s'.", //$NON-NLS-1$
|
||||
f, name, getOsAttrsXmlPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// does this <attr> have <enum> children?
|
||||
enumValues = parseEnumFlagValues(attrNode, "enum", name); //$NON-NLS-1$
|
||||
if (enumValues != null) {
|
||||
formats.add(AttributeInfo.Format.ENUM);
|
||||
}
|
||||
|
||||
// does this <attr> have <flag> children?
|
||||
flagValues = parseEnumFlagValues(attrNode, "flag", name); //$NON-NLS-1$
|
||||
if (flagValues != null) {
|
||||
formats.add(AttributeInfo.Format.FLAG);
|
||||
}
|
||||
|
||||
AttributeInfo info = new AttributeInfo(name,
|
||||
formats.toArray(new AttributeInfo.Format[formats.size()]));
|
||||
info.setEnumValues(enumValues);
|
||||
info.setFlagValues(flagValues);
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an XML node that represents an <attr> node, this method searches
|
||||
* if the node has any children nodes named "target" (e.g. "enum" or "flag").
|
||||
* Such nodes must have a "name" attribute.
|
||||
* <p/>
|
||||
* If "attrNode" is null, look for any <attr> that has the given attrNode
|
||||
* and the requested children nodes.
|
||||
* <p/>
|
||||
* This method collects all the possible names of these children nodes and
|
||||
* return them.
|
||||
*
|
||||
* @param attrNode The <attr> XML node
|
||||
* @param filter The child node to look for, either "enum" or "flag".
|
||||
* @param attrName The value of the name attribute of <attr>
|
||||
*
|
||||
* @return Null if there are no such children nodes, otherwise an array of length >= 1
|
||||
* of all the names of these children nodes.
|
||||
*/
|
||||
private String[] parseEnumFlagValues(Node attrNode, String filter, String attrName) {
|
||||
ArrayList<String> names = null;
|
||||
for (Node child = attrNode.getFirstChild(); child != null; child = child.getNextSibling()) {
|
||||
if (child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().equals(filter)) {
|
||||
Node nameNode = child.getAttributes().getNamedItem("name"); //$NON-NLS-1$
|
||||
if (nameNode == null) {
|
||||
AdtPlugin.log(IStatus.WARNING,
|
||||
"Missing name attribute in <attr name=\"%s\"><%s></attr>", //$NON-NLS-1$
|
||||
attrName, filter);
|
||||
} else {
|
||||
if (names == null) {
|
||||
names = new ArrayList<String>();
|
||||
}
|
||||
String name = nameNode.getNodeValue();
|
||||
names.add(name);
|
||||
|
||||
Node valueNode = child.getAttributes().getNamedItem("value"); //$NON-NLS-1$
|
||||
if (valueNode == null) {
|
||||
AdtPlugin.log(IStatus.WARNING,
|
||||
"Missing value attribute in <attr name=\"%s\"><%s name=\"%s\"></attr>", //$NON-NLS-1$
|
||||
attrName, filter, name);
|
||||
} else {
|
||||
String value = valueNode.getNodeValue();
|
||||
try {
|
||||
int i = value.startsWith("0x") ?
|
||||
Integer.parseInt(value.substring(2), 16 /* radix */) :
|
||||
Integer.parseInt(value);
|
||||
|
||||
Map<String, Integer> map = mEnumFlagValues.get(attrName);
|
||||
if (map == null) {
|
||||
map = new HashMap<String, Integer>();
|
||||
mEnumFlagValues.put(attrName, map);
|
||||
}
|
||||
map.put(name, Integer.valueOf(i));
|
||||
|
||||
} catch(NumberFormatException e) {
|
||||
AdtPlugin.log(e,
|
||||
"Value in <attr name=\"%s\"><%s name=\"%s\" value=\"%s\"></attr> is not a valid decimal or hexadecimal", //$NON-NLS-1$
|
||||
attrName, filter, name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return names == null ? null : names.toArray(new String[names.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the javadoc.
|
||||
* Only keeps the first sentence.
|
||||
* Removes and simplifies links and references.
|
||||
*/
|
||||
private String formatJavadoc(String comment) {
|
||||
if (comment == null) {
|
||||
return null;
|
||||
}
|
||||
// sanitize & collapse whitespace
|
||||
comment = comment.replaceAll("\\s+", " "); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
// take everything up to the first dot that is followed by a space or the end of the line.
|
||||
// I love regexps :-). For the curious, the regexp is:
|
||||
// - start of line
|
||||
// - ignore whitespace
|
||||
// - group:
|
||||
// - everything, not greedy
|
||||
// - non-capturing group (?: )
|
||||
// - end of string
|
||||
// or
|
||||
// - not preceded by a letter, a dot and another letter (for "i.e" and "e.g" )
|
||||
// (<! non-capturing zero-width negative look-behind)
|
||||
// - a dot
|
||||
// - followed by a space (?= non-capturing zero-width positive look-ahead)
|
||||
// - anything else is ignored
|
||||
comment = comment.replaceFirst("^\\s*(.*?(?:$|(?<![a-zA-Z]\\.[a-zA-Z])\\.(?=\\s))).*", "$1"); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
return comment;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.common.resources;
|
||||
|
||||
|
||||
/**
|
||||
* Information needed to represent a View or ViewGroup (aka Layout) item
|
||||
* in the layout hierarchy, as extracted from the main android.jar and the
|
||||
* associated attrs.xml.
|
||||
*/
|
||||
public class DeclareStyleableInfo {
|
||||
/** The style name, never null. */
|
||||
private String mStyleName;
|
||||
/** Attributes for this view or view group. Can be empty but never null. */
|
||||
private AttributeInfo[] mAttributes;
|
||||
/** Short javadoc. Can be null. */
|
||||
private String mJavaDoc;
|
||||
/** Optional name of the parents stylable. Can be null. */
|
||||
private String[] mParents;
|
||||
|
||||
public static class AttributeInfo {
|
||||
/** XML Name of the attribute */
|
||||
private String mName;
|
||||
|
||||
public enum Format {
|
||||
STRING,
|
||||
BOOLEAN,
|
||||
INTEGER,
|
||||
FLOAT,
|
||||
REFERENCE,
|
||||
COLOR,
|
||||
DIMENSION,
|
||||
FRACTION,
|
||||
ENUM,
|
||||
FLAG,
|
||||
}
|
||||
|
||||
/** Formats of the attribute. Cannot be null. Should have at least one format. */
|
||||
private Format[] mFormats;
|
||||
/** Values for enum. null for other types. */
|
||||
private String[] mEnumValues;
|
||||
/** Values for flag. null for other types. */
|
||||
private String[] mFlagValues;
|
||||
/** Short javadoc */
|
||||
private String mJavaDoc;
|
||||
|
||||
/**
|
||||
* @param name The XML Name of the attribute
|
||||
* @param formats The formats of the attribute. Cannot be null.
|
||||
* Should have at least one format.
|
||||
*/
|
||||
public AttributeInfo(String name, Format[] formats) {
|
||||
mName = name;
|
||||
mFormats = formats;
|
||||
}
|
||||
|
||||
public AttributeInfo(AttributeInfo info) {
|
||||
mName = info.mName;
|
||||
mFormats = info.mFormats;
|
||||
mEnumValues = info.mEnumValues;
|
||||
mFlagValues = info.mFlagValues;
|
||||
mJavaDoc = info.mJavaDoc;
|
||||
}
|
||||
|
||||
/** Returns the XML Name of the attribute */
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
/** Returns the formats of the attribute. Cannot be null.
|
||||
* Should have at least one format. */
|
||||
public Format[] getFormats() {
|
||||
return mFormats;
|
||||
}
|
||||
/** Returns the values for enums. null for other types. */
|
||||
public String[] getEnumValues() {
|
||||
return mEnumValues;
|
||||
}
|
||||
/** Returns the values for flags. null for other types. */
|
||||
public String[] getFlagValues() {
|
||||
return mFlagValues;
|
||||
}
|
||||
/** Returns a short javadoc */
|
||||
public String getJavaDoc() {
|
||||
return mJavaDoc;
|
||||
}
|
||||
|
||||
/** Sets the values for enums. null for other types. */
|
||||
public void setEnumValues(String[] values) {
|
||||
mEnumValues = values;
|
||||
}
|
||||
/** Sets the values for flags. null for other types. */
|
||||
public void setFlagValues(String[] values) {
|
||||
mFlagValues = values;
|
||||
}
|
||||
/** Sets a short javadoc */
|
||||
public void setJavaDoc(String javaDoc) {
|
||||
mJavaDoc = javaDoc;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// --------
|
||||
|
||||
/**
|
||||
* Creates a new {@link DeclareStyleableInfo}.
|
||||
*
|
||||
* @param styleName The name of the style. Should not be empty nor null.
|
||||
* @param attributes The initial list of attributes. Can be null.
|
||||
*/
|
||||
public DeclareStyleableInfo(String styleName, AttributeInfo[] attributes) {
|
||||
mStyleName = styleName;
|
||||
mAttributes = attributes == null ? new AttributeInfo[0] : attributes;
|
||||
}
|
||||
|
||||
/** Returns style name */
|
||||
public String getStyleName() {
|
||||
return mStyleName;
|
||||
}
|
||||
|
||||
/** Returns the attributes for this view or view group. Maybe empty but not null. */
|
||||
public AttributeInfo[] getAttributes() {
|
||||
return mAttributes;
|
||||
}
|
||||
|
||||
/** Sets the list of attributes for this View or ViewGroup. */
|
||||
public void setAttributes(AttributeInfo[] attributes) {
|
||||
mAttributes = attributes;
|
||||
}
|
||||
|
||||
/** Returns a short javadoc */
|
||||
public String getJavaDoc() {
|
||||
return mJavaDoc;
|
||||
}
|
||||
|
||||
/** Sets the javadoc. */
|
||||
public void setJavaDoc(String javaDoc) {
|
||||
mJavaDoc = javaDoc;
|
||||
}
|
||||
|
||||
/** Sets the name of the parents styleable. Can be null. */
|
||||
public void setParents(String[] parents) {
|
||||
mParents = parents;
|
||||
}
|
||||
|
||||
/** Returns the name of the parents styleable. Can be null. */
|
||||
public String[] getParents() {
|
||||
return mParents;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.common.resources;
|
||||
|
||||
/**
|
||||
* Classes which implements this interface provides a method indicating the state of a resource of
|
||||
* type {@link ResourceType#ID}.
|
||||
*/
|
||||
public interface IIdResourceItem {
|
||||
/**
|
||||
* Returns whether the ID resource has been declared inline inside another resource XML file.
|
||||
*/
|
||||
public boolean isDeclaredInline();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.common.resources;
|
||||
|
||||
/**
|
||||
* Classes which implement this interface provide a method that deals with
|
||||
* a path change.
|
||||
*/
|
||||
public interface IPathChangedListener {
|
||||
/**
|
||||
* Sent when the location of the android sdk directory changed.
|
||||
* @param osPath The new android sdk directory location.
|
||||
*/
|
||||
public void pathChanged(String osPath);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.common.resources;
|
||||
|
||||
/**
|
||||
* A repository of resources. This allows access to the resource by {@link ResourceType}.
|
||||
*/
|
||||
public interface IResourceRepository {
|
||||
|
||||
/**
|
||||
* Returns the present {@link ResourceType}s in the project.
|
||||
* @return an array containing all the type of resources existing in the project.
|
||||
*/
|
||||
public abstract ResourceType[] getAvailableResourceTypes();
|
||||
|
||||
/**
|
||||
* Returns an array of the existing resource for the specified type.
|
||||
* @param type the type of the resources to return
|
||||
*/
|
||||
public abstract ResourceItem[] getResources(ResourceType type);
|
||||
|
||||
/**
|
||||
* Returns whether resources of the specified type are present.
|
||||
* @param type the type of the resources to check.
|
||||
*/
|
||||
public abstract boolean hasResources(ResourceType type);
|
||||
|
||||
/**
|
||||
* Returns whether the repository is a system repository.
|
||||
*/
|
||||
public abstract boolean isSystemRepository();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.common.resources;
|
||||
|
||||
/**
|
||||
* Base class representing a Resource Item, as returned by a {@link IResourceRepository}.
|
||||
*/
|
||||
public class ResourceItem implements Comparable<ResourceItem> {
|
||||
|
||||
private final String mName;
|
||||
|
||||
/**
|
||||
* Constructs a new ResourceItem
|
||||
* @param name the name of the resource as it appears in the XML and R.java files.
|
||||
*/
|
||||
public ResourceItem(String name) {
|
||||
mName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the resource item.
|
||||
*/
|
||||
public final String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the {@link ResourceItem} to another.
|
||||
* @param other the ResourceItem to be compared to.
|
||||
*/
|
||||
public int compareTo(ResourceItem other) {
|
||||
return mName.compareTo(other.mName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.common.resources;
|
||||
|
||||
/**
|
||||
* Enum representing a type of compiled resource.
|
||||
*/
|
||||
public enum ResourceType {
|
||||
ANIM("anim", "Animation"), //$NON-NLS-1$
|
||||
ARRAY("array", "Array", "string-array", "integer-array"), //$NON-NLS-1$ //$NON-NLS-3$ //$NON-NLS-4$
|
||||
ATTR("attr", "Attr"), //$NON-NLS-1$
|
||||
COLOR("color", "Color"), //$NON-NLS-1$
|
||||
DIMEN("dimen", "Dimension"), //$NON-NLS-1$
|
||||
DRAWABLE("drawable", "Drawable"), //$NON-NLS-1$
|
||||
ID("id", "ID"), //$NON-NLS-1$
|
||||
LAYOUT("layout", "Layout"), //$NON-NLS-1$
|
||||
MENU("menu", "Menu"), //$NON-NLS-1$
|
||||
RAW("raw", "Raw"), //$NON-NLS-1$
|
||||
STRING("string", "String"), //$NON-NLS-1$
|
||||
STYLE("style", "Style"), //$NON-NLS-1$
|
||||
STYLEABLE("styleable", "Styleable"), //$NON-NLS-1$
|
||||
XML("xml", "XML"); //$NON-NLS-1$
|
||||
|
||||
private final String mName;
|
||||
private final String mDisplayName;
|
||||
private final String[] mAlternateXmlNames;
|
||||
|
||||
ResourceType(String name, String displayName, String... alternateXmlNames) {
|
||||
mName = name;
|
||||
mDisplayName = displayName;
|
||||
mAlternateXmlNames = alternateXmlNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resource type name, as used by XML files.
|
||||
*/
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a translated display name for the resource type.
|
||||
*/
|
||||
public String getDisplayName() {
|
||||
return mDisplayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the enum by its name as it appears in the XML or the R class.
|
||||
* @param name name of the resource
|
||||
* @return the matching {@link ResourceType} or <code>null</code> if no match was found.
|
||||
*/
|
||||
public static ResourceType getEnum(String name) {
|
||||
for (ResourceType rType : values()) {
|
||||
if (rType.mName.equals(name)) {
|
||||
return rType;
|
||||
} else if (rType.mAlternateXmlNames != null) {
|
||||
// if there are alternate Xml Names, we test those too
|
||||
for (String alternate : rType.mAlternateXmlNames) {
|
||||
if (alternate.equals(name)) {
|
||||
return rType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a formatted string usable in an XML to use the specified {@link ResourceItem}.
|
||||
* @param resourceItem The resource item.
|
||||
* @param system Whether this is a system resource or a project resource.
|
||||
* @return a string in the format @[type]/[name]
|
||||
*/
|
||||
public String getXmlString(ResourceItem resourceItem, boolean system) {
|
||||
if (this == ID && resourceItem instanceof IIdResourceItem) {
|
||||
IIdResourceItem idResource = (IIdResourceItem)resourceItem;
|
||||
if (idResource.isDeclaredInline()) {
|
||||
return (system?"@android:":"@+") + mName + "/" + resourceItem.getName(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
|
||||
}
|
||||
}
|
||||
|
||||
return (system?"@android:":"@") + mName + "/" + resourceItem.getName(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with all the names defined by this enum.
|
||||
*/
|
||||
public static String[] getNames() {
|
||||
ResourceType[] values = values();
|
||||
String[] names = new String[values.length];
|
||||
for (int i = values.length - 1; i >= 0; --i) {
|
||||
names[i] = values[i].getName();
|
||||
}
|
||||
return names;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.common.resources;
|
||||
|
||||
import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo;
|
||||
|
||||
/**
|
||||
* Information needed to represent a View or ViewGroup (aka Layout) item
|
||||
* in the layout hierarchy, as extracted from the main android.jar and the
|
||||
* associated attrs.xml.
|
||||
*/
|
||||
public class ViewClassInfo {
|
||||
/** Is this a layout class (i.e. ViewGroup) or just a view? */
|
||||
private boolean mIsLayout;
|
||||
/** FQCN e.g. android.view.View, never null. */
|
||||
private String mCanonicalClassName;
|
||||
/** Short class name, e.g. View, never null. */
|
||||
private String mShortClassName;
|
||||
/** Super class. Can be null. */
|
||||
private ViewClassInfo mSuperClass;
|
||||
/** Short javadoc. Can be null. */
|
||||
private String mJavaDoc;
|
||||
/** Attributes for this view or view group. Can be empty but never null. */
|
||||
private AttributeInfo[] mAttributes;
|
||||
|
||||
public static class LayoutParamsInfo {
|
||||
/** Short class name, e.g. LayoutData, never null. */
|
||||
private String mShortClassName;
|
||||
/** ViewLayout class info owning this layout data */
|
||||
private ViewClassInfo mViewLayoutClass;
|
||||
/** Super class. Can be null. */
|
||||
private LayoutParamsInfo mSuperClass;
|
||||
/** Layout Data Attributes for layout classes. Can be empty but not null. */
|
||||
private AttributeInfo[] mAttributes;
|
||||
|
||||
public LayoutParamsInfo(ViewClassInfo enclosingViewClassInfo,
|
||||
String shortClassName, LayoutParamsInfo superClassInfo) {
|
||||
mShortClassName = shortClassName;
|
||||
mViewLayoutClass = enclosingViewClassInfo;
|
||||
mSuperClass = superClassInfo;
|
||||
mAttributes = new AttributeInfo[0];
|
||||
}
|
||||
|
||||
/** Returns short class name, e.g. "LayoutData" */
|
||||
public String getShortClassName() {
|
||||
return mShortClassName;
|
||||
}
|
||||
/** Returns the ViewLayout class info enclosing this layout data. Cannot null. */
|
||||
public ViewClassInfo getViewLayoutClass() {
|
||||
return mViewLayoutClass;
|
||||
}
|
||||
/** Returns the super class info. Can be null. */
|
||||
public LayoutParamsInfo getSuperClass() {
|
||||
return mSuperClass;
|
||||
}
|
||||
/** Returns the LayoutData attributes. Can be empty but not null. */
|
||||
public AttributeInfo[] getAttributes() {
|
||||
return mAttributes;
|
||||
}
|
||||
/** Sets the LayoutData attributes. Can be empty but not null. */
|
||||
public void setAttributes(AttributeInfo[] attributes) {
|
||||
mAttributes = attributes;
|
||||
}
|
||||
}
|
||||
|
||||
/** Layout data info for a layout class. Null for all non-layout classes and always
|
||||
* non-null for a layout class. */
|
||||
public LayoutParamsInfo mLayoutData;
|
||||
|
||||
// --------
|
||||
|
||||
public ViewClassInfo(boolean isLayout, String canonicalClassName, String shortClassName) {
|
||||
mIsLayout = isLayout;
|
||||
mCanonicalClassName = canonicalClassName;
|
||||
mShortClassName = shortClassName;
|
||||
mAttributes = new AttributeInfo[0];
|
||||
}
|
||||
|
||||
/** Returns whether this is a layout class (i.e. ViewGroup) or just a View */
|
||||
public boolean isLayout() {
|
||||
return mIsLayout;
|
||||
}
|
||||
|
||||
/** Returns FQCN e.g. "android.view.View" */
|
||||
public String getCanonicalClassName() {
|
||||
return mCanonicalClassName;
|
||||
}
|
||||
|
||||
/** Returns short class name, e.g. "View" */
|
||||
public String getShortClassName() {
|
||||
return mShortClassName;
|
||||
}
|
||||
|
||||
/** Returns the super class. Can be null. */
|
||||
public ViewClassInfo getSuperClass() {
|
||||
return mSuperClass;
|
||||
}
|
||||
|
||||
/** Returns a short javadoc */
|
||||
public String getJavaDoc() {
|
||||
return mJavaDoc;
|
||||
}
|
||||
|
||||
/** Returns the attributes for this view or view group. Maybe empty but not null. */
|
||||
public AttributeInfo[] getAttributes() {
|
||||
return mAttributes;
|
||||
}
|
||||
|
||||
/** Returns the LayoutData info for layout classes. Null for non-layout view classes. */
|
||||
public LayoutParamsInfo getLayoutData() {
|
||||
return mLayoutData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a link on the info of the super class of this View or ViewGroup.
|
||||
* <p/>
|
||||
* The super class info must be of the same kind (i.e. group to group or view to view)
|
||||
* except for the top ViewGroup which links to the View info.
|
||||
* <p/>
|
||||
* The super class cannot be null except for the top View info.
|
||||
*/
|
||||
public void setSuperClass(ViewClassInfo superClass) {
|
||||
mSuperClass = superClass;
|
||||
}
|
||||
|
||||
/** Sets the javadoc for this View or ViewGroup. */
|
||||
public void setJavaDoc(String javaDoc) {
|
||||
mJavaDoc = javaDoc;
|
||||
}
|
||||
|
||||
/** Sets the list of attributes for this View or ViewGroup. */
|
||||
public void setAttributes(AttributeInfo[] attributes) {
|
||||
mAttributes = attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link LayoutParamsInfo} for layout classes.
|
||||
* Does nothing for non-layout view classes.
|
||||
*/
|
||||
public void setLayoutParams(LayoutParamsInfo layoutData) {
|
||||
if (mIsLayout) {
|
||||
mLayoutData = layoutData;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,767 @@
|
||||
/*
|
||||
* 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.editors;
|
||||
|
||||
import com.android.ide.eclipse.adt.sdk.AndroidTargetData;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor;
|
||||
import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils;
|
||||
import com.android.ide.eclipse.editors.descriptors.ElementDescriptor;
|
||||
import com.android.ide.eclipse.editors.descriptors.IDescriptorProvider;
|
||||
import com.android.ide.eclipse.editors.descriptors.SeparatorAttributeDescriptor;
|
||||
import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor;
|
||||
import com.android.ide.eclipse.editors.descriptors.TextValueDescriptor;
|
||||
import com.android.ide.eclipse.editors.descriptors.XmlnsAttributeDescriptor;
|
||||
import com.android.ide.eclipse.editors.uimodel.UiAttributeNode;
|
||||
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
|
||||
import com.android.ide.eclipse.editors.uimodel.UiFlagAttributeNode;
|
||||
|
||||
import org.eclipse.jface.text.BadLocationException;
|
||||
import org.eclipse.jface.text.IDocument;
|
||||
import org.eclipse.jface.text.ITextViewer;
|
||||
import org.eclipse.jface.text.TextSelection;
|
||||
import org.eclipse.jface.text.contentassist.CompletionProposal;
|
||||
import org.eclipse.jface.text.contentassist.ICompletionProposal;
|
||||
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
|
||||
import org.eclipse.jface.text.contentassist.IContextInformation;
|
||||
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
|
||||
import org.eclipse.jface.text.source.ISourceViewer;
|
||||
import org.eclipse.jface.viewers.ISelection;
|
||||
import org.eclipse.swt.graphics.Image;
|
||||
import org.eclipse.ui.IEditorPart;
|
||||
import org.eclipse.ui.IWorkbenchPage;
|
||||
import org.eclipse.ui.IWorkbenchWindow;
|
||||
import org.eclipse.ui.PlatformUI;
|
||||
import org.eclipse.wst.sse.core.StructuredModelManager;
|
||||
import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
|
||||
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Content Assist Processor for Android XML files
|
||||
*/
|
||||
public abstract class AndroidContentAssist implements IContentAssistProcessor {
|
||||
|
||||
/** Regexp to detect a full attribute after an element tag.
|
||||
* <pre>Syntax:
|
||||
* name = "..." quoted string with all but < and "
|
||||
* or:
|
||||
* name = '...' quoted string with all but < and '
|
||||
* </pre>
|
||||
*/
|
||||
private static Pattern sFirstAttribute = Pattern.compile(
|
||||
"^ *[a-zA-Z_:]+ *= *(?:\"[^<\"]*\"|'[^<']*')"); //$NON-NLS-1$
|
||||
|
||||
/** Regexp to detect an element tag name */
|
||||
private static Pattern sFirstElementWord = Pattern.compile("^[a-zA-Z0-9_:]+"); //$NON-NLS-1$
|
||||
|
||||
/** Regexp to detect whitespace */
|
||||
private static Pattern sWhitespace = Pattern.compile("\\s+"); //$NON-NLS-1$
|
||||
|
||||
protected final static String ROOT_ELEMENT = "";
|
||||
|
||||
/** Descriptor of the root of the XML hierarchy. This a "fake" ElementDescriptor which
|
||||
* is used to list all the possible roots given by actual implementations.
|
||||
* DO NOT USE DIRECTLY. Call {@link #getRootDescriptor()} instead. */
|
||||
private ElementDescriptor mRootDescriptor;
|
||||
|
||||
private final int mDescriptorId;
|
||||
|
||||
private AndroidEditor mEditor;
|
||||
|
||||
/**
|
||||
* Constructor for AndroidContentAssist
|
||||
* @param rootElementDescriptors The valid root elements of the XML hierarchy
|
||||
*/
|
||||
public AndroidContentAssist(int descriptorId) {
|
||||
mDescriptorId = descriptorId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of completion proposals based on the
|
||||
* specified location within the document that corresponds
|
||||
* to the current cursor position within the text viewer.
|
||||
*
|
||||
* @param viewer the viewer whose document is used to compute the proposals
|
||||
* @param offset an offset within the document for which completions should be computed
|
||||
* @return an array of completion proposals or <code>null</code> if no proposals are possible
|
||||
*
|
||||
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeCompletionProposals(org.eclipse.jface.text.ITextViewer, int)
|
||||
*/
|
||||
public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
|
||||
|
||||
if (mEditor == null) {
|
||||
mEditor = getAndroidEditor(viewer);
|
||||
}
|
||||
|
||||
UiElementNode rootUiNode = mEditor.getUiRootNode();
|
||||
|
||||
Object[] choices = null; /* An array of ElementDescriptor, or AttributeDescriptor
|
||||
or String or null */
|
||||
String parent = ""; //$NON-NLS-1$
|
||||
String wordPrefix = extractElementPrefix(viewer, offset);
|
||||
char needTag = 0;
|
||||
boolean isElement = false;
|
||||
boolean isAttribute = false;
|
||||
|
||||
Node currentNode = getNode(viewer, offset);
|
||||
if (currentNode == null)
|
||||
return null;
|
||||
|
||||
// check to see if we can find a UiElementNode matching this XML node
|
||||
UiElementNode currentUiNode =
|
||||
rootUiNode == null ? null : rootUiNode.findXmlNode(currentNode);
|
||||
|
||||
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
|
||||
parent = currentNode.getNodeName();
|
||||
|
||||
if (wordPrefix.equals(parent)) {
|
||||
// We are still editing the element's tag name, not the attributes
|
||||
// (the element's tag name may not even be complete)
|
||||
isElement = true;
|
||||
choices = getChoicesForElement(parent, currentNode);
|
||||
} else {
|
||||
// We're not editing the current node name, so we might be editing its
|
||||
// attributes instead...
|
||||
isAttribute = true;
|
||||
AttribInfo info = parseAttributeInfo(viewer, offset);
|
||||
if (info != null) {
|
||||
// We're editing attributes in an element node (either the attributes' names
|
||||
// or their values).
|
||||
choices = getChoicesForAttribute(parent, currentNode, currentUiNode, info);
|
||||
|
||||
if (info.correctedPrefix != null) {
|
||||
wordPrefix = info.correctedPrefix;
|
||||
}
|
||||
needTag = info.needTag;
|
||||
}
|
||||
}
|
||||
} else if (currentNode.getNodeType() == Node.TEXT_NODE) {
|
||||
isElement = true;
|
||||
// Examine the parent of the text node.
|
||||
choices = getChoicesForTextNode(currentNode);
|
||||
}
|
||||
|
||||
// Abort if we can't recognize the context or there are no completion choices
|
||||
if (choices == null || choices.length == 0) return null;
|
||||
|
||||
if (isElement) {
|
||||
// If we found some suggestions, do we need to add an opening "<" bracket
|
||||
// for the element? We don't if the cursor is right after "<" or "</".
|
||||
// Per XML Spec, there's no whitespace between "<" or "</" and the tag name.
|
||||
int offset2 = offset - wordPrefix.length() - 1;
|
||||
int c1 = extractChar(viewer, offset2);
|
||||
if (!((c1 == '<') || (c1 == '/' && extractChar(viewer, offset2 - 1) == '<'))) {
|
||||
needTag = '<';
|
||||
}
|
||||
}
|
||||
|
||||
// get the selection length
|
||||
int selectionLength = 0;
|
||||
ISelection selection = viewer.getSelectionProvider().getSelection();
|
||||
if (selection instanceof TextSelection) {
|
||||
TextSelection textSelection = (TextSelection)selection;
|
||||
selectionLength = textSelection.getLength();
|
||||
}
|
||||
|
||||
return computeProposals(offset, currentNode, choices, wordPrefix, needTag,
|
||||
isAttribute, selectionLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the namespace prefix matching the Android Resource URI.
|
||||
* If no such declaration is found, returns the default "android" prefix.
|
||||
*
|
||||
* @param node The current node. Must not be null.
|
||||
* @param nsUri The namespace URI of which the prefix is to be found,
|
||||
* e.g. AndroidConstants.NS_RESOURCES
|
||||
* @return The first prefix declared or the default "android" prefix.
|
||||
*/
|
||||
private String lookupNamespacePrefix(Node node, String nsUri) {
|
||||
// Note: Node.lookupPrefix is not implemented in wst/xml/core NodeImpl.java
|
||||
// The following emulates this:
|
||||
// String prefix = node.lookupPrefix(AndroidConstants.NS_RESOURCES);
|
||||
|
||||
if (XmlnsAttributeDescriptor.XMLNS_URI.equals(nsUri)) {
|
||||
return "xmlns"; //$NON-NLS-1$
|
||||
}
|
||||
|
||||
HashSet<String> visited = new HashSet<String>();
|
||||
|
||||
String prefix = null;
|
||||
for (; prefix == null &&
|
||||
node != null &&
|
||||
node.getNodeType() == Node.ELEMENT_NODE;
|
||||
node = node.getParentNode()) {
|
||||
NamedNodeMap attrs = node.getAttributes();
|
||||
for (int n = attrs.getLength() - 1; n >= 0; --n) {
|
||||
Node attr = attrs.item(n);
|
||||
if ("xmlns".equals(attr.getPrefix())) { //$NON-NLS-1$
|
||||
String uri = attr.getNodeValue();
|
||||
if (AndroidConstants.NS_RESOURCES.equals(uri)) {
|
||||
return attr.getLocalName();
|
||||
}
|
||||
visited.add(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use a sensible default prefix if we can't find one.
|
||||
// We need to make sure the prefix is not one that was declared in the scope
|
||||
// visited above.
|
||||
prefix = AndroidConstants.NS_RESOURCES.equals(nsUri) ? "android" : "ns"; //$NON-NLS-1$ //$NON-NLS-2$
|
||||
String base = prefix;
|
||||
for (int i = 1; visited.contains(prefix); i++) {
|
||||
prefix = base + Integer.toString(i);
|
||||
}
|
||||
return prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the choices when the user is editing the name of an XML element.
|
||||
* <p/>
|
||||
* The user is editing the name of an element (the "parent").
|
||||
* Find the grand-parent and if one is found, return its children element list.
|
||||
* The name which is being edited should be one of those.
|
||||
* <p/>
|
||||
* Example: <manifest><applic*cursor* => returns the list of all elements that
|
||||
* can be found under <manifest>, of which <application> is one of the choices.
|
||||
*
|
||||
* @return an ElementDescriptor[] or null if no valid element was found.
|
||||
*/
|
||||
private Object[] getChoicesForElement(String parent, Node current_node) {
|
||||
ElementDescriptor grandparent = null;
|
||||
if (current_node.getParentNode().getNodeType() == Node.ELEMENT_NODE) {
|
||||
grandparent = getDescriptor(current_node.getParentNode().getNodeName());
|
||||
} else if (current_node.getParentNode().getNodeType() == Node.DOCUMENT_NODE) {
|
||||
grandparent = getRootDescriptor();
|
||||
}
|
||||
if (grandparent != null) {
|
||||
for (ElementDescriptor e : grandparent.getChildren()) {
|
||||
if (e.getXmlName().startsWith(parent)) {
|
||||
return grandparent.getChildren();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the choices when the user is editing an XML attribute.
|
||||
* <p/>
|
||||
* In input, attrInfo contains details on the analyzed context, namely whether the
|
||||
* user is editing an attribute value (isInValue) or an attribute name.
|
||||
* <p/>
|
||||
* In output, attrInfo also contains two possible new values (this is a hack to circumvent
|
||||
* the lack of out-parameters in Java):
|
||||
* - AttribInfo.correctedPrefix if the user has been editing an attribute value and it has
|
||||
* been detected that what the user typed is different from what extractElementPrefix()
|
||||
* predicted. This happens because extractElementPrefix() stops when a character that
|
||||
* cannot be an element name appears whereas parseAttributeInfo() uses a grammar more
|
||||
* lenient as suitable for attribute values.
|
||||
* - AttribInfo.needTag will be non-zero if we find that the attribute completion proposal
|
||||
* must be double-quoted.
|
||||
* @param currentUiNode
|
||||
*
|
||||
* @return an AttributeDescriptor[] if the user is editing an attribute name.
|
||||
* a String[] if the user is editing an attribute value with some known values,
|
||||
* or null if nothing is known about the context.
|
||||
*/
|
||||
private Object[] getChoicesForAttribute(String parent,
|
||||
Node currentNode, UiElementNode currentUiNode, AttribInfo attrInfo) {
|
||||
Object[] choices = null;
|
||||
if (attrInfo.isInValue) {
|
||||
// Editing an attribute's value... Get the attribute name and then the
|
||||
// possible choice for the tuple(parent,attribute)
|
||||
String value = attrInfo.value;
|
||||
if (value.startsWith("'") || value.startsWith("\"")) { //$NON-NLS-1$ //$NON-NLS-2$
|
||||
value = value.substring(1);
|
||||
// The prefix that was found at the beginning only scan for characters
|
||||
// valid of tag name. We now know the real prefix for this attribute's
|
||||
// value, which is needed to generate the completion choices below.
|
||||
attrInfo.correctedPrefix = value;
|
||||
} else {
|
||||
attrInfo.needTag = '"';
|
||||
}
|
||||
|
||||
if (currentUiNode != null) {
|
||||
// look for an UI attribute matching the current attribute name
|
||||
String attrName = attrInfo.name;
|
||||
|
||||
UiAttributeNode currAttrNode = null;
|
||||
for (UiAttributeNode attrNode : currentUiNode.getUiAttributes()) {
|
||||
if (attrNode.getDescriptor().getXmlLocalName().equals(attrName)) {
|
||||
currAttrNode = attrNode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currAttrNode != null) {
|
||||
choices = currAttrNode.getPossibleValues();
|
||||
|
||||
if (currAttrNode instanceof UiFlagAttributeNode) {
|
||||
// A "flag" can consist of several values separated by "or" (|).
|
||||
// If the correct prefix contains such a pipe character, we change
|
||||
// it so that only the currently edited value is completed.
|
||||
int pos = value.indexOf('|');
|
||||
if (pos >= 0) {
|
||||
attrInfo.correctedPrefix = value = value.substring(pos + 1);
|
||||
attrInfo.needTag = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (choices == null) {
|
||||
// fallback on the older descriptor-only based lookup.
|
||||
|
||||
// in order to properly handle the special case of the name attribute in
|
||||
// the action tag, we need the grandparent of the action node, to know
|
||||
// what type of actions we need.
|
||||
// e.g. activity -> intent-filter -> action[@name]
|
||||
String greatGrandParentName = null;
|
||||
Node grandParent = currentNode.getParentNode();
|
||||
if (grandParent != null) {
|
||||
Node greatGrandParent = grandParent.getParentNode();
|
||||
if (greatGrandParent != null) {
|
||||
greatGrandParentName = greatGrandParent.getLocalName();
|
||||
}
|
||||
}
|
||||
|
||||
AndroidTargetData data = mEditor.getTargetData();
|
||||
if (data != null) {
|
||||
choices = data.getAttributeValues(parent, attrInfo.name, greatGrandParentName);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Editing an attribute's name... Get attributes valid for the parent node.
|
||||
if (currentUiNode != null) {
|
||||
choices = currentUiNode.getAttributeDescriptors();
|
||||
} else {
|
||||
ElementDescriptor parent_desc = getDescriptor(parent);
|
||||
choices = parent_desc.getAttributes();
|
||||
}
|
||||
}
|
||||
return choices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the choices when the user is editing an XML text node.
|
||||
* <p/>
|
||||
* This means the user is editing outside of any XML element or attribute.
|
||||
* Simply return the list of XML elements that can be present there, based on the
|
||||
* parent of the current node.
|
||||
*
|
||||
* @return An ElementDescriptor[] or null.
|
||||
*/
|
||||
private Object[] getChoicesForTextNode(Node currentNode) {
|
||||
Object[] choices = null;
|
||||
String parent;
|
||||
Node parent_node = currentNode.getParentNode();
|
||||
if (parent_node.getNodeType() == Node.ELEMENT_NODE) {
|
||||
// We're editing a text node which parent is an element node. Limit
|
||||
// content assist to elements valid for the parent.
|
||||
parent = parent_node.getNodeName();
|
||||
ElementDescriptor desc = getDescriptor(parent);
|
||||
if (desc != null) {
|
||||
choices = desc.getChildren();
|
||||
}
|
||||
} else if (parent_node.getNodeType() == Node.DOCUMENT_NODE) {
|
||||
// We're editing a text node at the first level (i.e. root node).
|
||||
// Limit content assist to the only valid root elements.
|
||||
choices = getRootDescriptor().getChildren();
|
||||
}
|
||||
return choices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of choices found, generates the proposals to be displayed to the user.
|
||||
* <p/>
|
||||
* Choices is an object array. Items of the array can be:
|
||||
* - ElementDescriptor: a possible element descriptor which XML name should be completed.
|
||||
* - AttributeDescriptor: a possible attribute descriptor which XML name should be completed.
|
||||
* - String: string values to display as-is to the user. Typically those are possible
|
||||
* values for a given attribute.
|
||||
*
|
||||
* @return The ICompletionProposal[] to display to the user.
|
||||
*/
|
||||
private ICompletionProposal[] computeProposals(int offset, Node currentNode,
|
||||
Object[] choices, String wordPrefix, char need_tag,
|
||||
boolean is_attribute, int selectionLength) {
|
||||
ArrayList<CompletionProposal> proposals = new ArrayList<CompletionProposal>();
|
||||
HashMap<String, String> nsUriMap = new HashMap<String, String>();
|
||||
|
||||
for (Object choice : choices) {
|
||||
String keyword = null;
|
||||
String nsPrefix = null;
|
||||
Image icon = null;
|
||||
String tooltip = null;
|
||||
if (choice instanceof ElementDescriptor) {
|
||||
keyword = ((ElementDescriptor)choice).getXmlName();
|
||||
icon = ((ElementDescriptor)choice).getIcon();
|
||||
tooltip = DescriptorsUtils.formatTooltip(((ElementDescriptor)choice).getTooltip());
|
||||
} else if (choice instanceof TextValueDescriptor) {
|
||||
continue; // Value nodes are not part of the completion choices
|
||||
} else if (choice instanceof SeparatorAttributeDescriptor) {
|
||||
continue; // not real attribute descriptors
|
||||
} else if (choice instanceof AttributeDescriptor) {
|
||||
keyword = ((AttributeDescriptor)choice).getXmlLocalName();
|
||||
icon = ((AttributeDescriptor)choice).getIcon();
|
||||
if (choice instanceof TextAttributeDescriptor) {
|
||||
tooltip = ((TextAttributeDescriptor) choice).getTooltip();
|
||||
}
|
||||
|
||||
String nsUri = ((AttributeDescriptor)choice).getNamespaceUri();
|
||||
nsPrefix = nsUriMap.get(nsUri);
|
||||
if (nsPrefix == null) {
|
||||
nsPrefix = lookupNamespacePrefix(currentNode, nsUri);
|
||||
nsUriMap.put(nsUri, nsPrefix);
|
||||
}
|
||||
if (nsPrefix != null) {
|
||||
nsPrefix += ":"; //$NON-NLS-1$
|
||||
}
|
||||
|
||||
} else if (choice instanceof String) {
|
||||
keyword = (String) choice;
|
||||
} else {
|
||||
continue; // discard unknown choice
|
||||
}
|
||||
|
||||
if (keyword.startsWith(wordPrefix) ||
|
||||
(nsPrefix != null && keyword.startsWith(nsPrefix))) {
|
||||
if (nsPrefix != null) {
|
||||
keyword = nsPrefix + keyword;
|
||||
}
|
||||
String end_tag = ""; //$NON-NLS-1$
|
||||
if (need_tag != 0) {
|
||||
if (need_tag == '"') {
|
||||
keyword = need_tag + keyword;
|
||||
end_tag = String.valueOf(need_tag);
|
||||
} else if (need_tag == '<') {
|
||||
if (elementCanHaveChildren(choice)) {
|
||||
end_tag = String.format("></%1$s>", keyword); //$NON-NLS-1$
|
||||
keyword = need_tag + keyword;
|
||||
} else {
|
||||
keyword = need_tag + keyword;
|
||||
end_tag = "/>"; //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
}
|
||||
CompletionProposal proposal = new CompletionProposal(
|
||||
keyword + end_tag, // String replacementString
|
||||
offset - wordPrefix.length(), // int replacementOffset
|
||||
wordPrefix.length() + selectionLength, // int replacementLength
|
||||
keyword.length(), // int cursorPosition (rel. to rplcmntOffset)
|
||||
icon, // Image image
|
||||
null, // String displayString
|
||||
null, // IContextInformation contextInformation
|
||||
tooltip // String additionalProposalInfo
|
||||
);
|
||||
|
||||
proposals.add(proposal);
|
||||
}
|
||||
}
|
||||
|
||||
return proposals.toArray(new ICompletionProposal[proposals.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this descriptor describes an element that can potentially
|
||||
* have children (either sub-elements or text value). If an element can have children,
|
||||
* we want to explicitly write an opening and a separate closing tag.
|
||||
* <p/>
|
||||
* Elements can have children if the descriptor has children element descriptors
|
||||
* or if one of the attributes is a TextValueDescriptor.
|
||||
*
|
||||
* @param descriptor An ElementDescriptor or an AttributeDescriptor
|
||||
* @return True if the descriptor is an ElementDescriptor that can have children or a text value
|
||||
*/
|
||||
private boolean elementCanHaveChildren(Object descriptor) {
|
||||
if (descriptor instanceof ElementDescriptor) {
|
||||
ElementDescriptor desc = (ElementDescriptor) descriptor;
|
||||
if (desc.hasChildren()) {
|
||||
return true;
|
||||
}
|
||||
for (AttributeDescriptor attr_desc : desc.getAttributes()) {
|
||||
if (attr_desc instanceof TextValueDescriptor) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the element descriptor matching a given XML node name or null if it can't be
|
||||
* found.
|
||||
* <p/>
|
||||
* This is simplistic; ideally we should consider the parent's chain to make sure we
|
||||
* can differentiate between different hierarchy trees. Right now the first match found
|
||||
* is returned.
|
||||
*/
|
||||
private ElementDescriptor getDescriptor(String nodeName) {
|
||||
return getRootDescriptor().findChildrenDescriptor(nodeName, true /* recursive */);
|
||||
}
|
||||
|
||||
public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the characters which when entered by the user should
|
||||
* automatically trigger the presentation of possible completions.
|
||||
*
|
||||
* In our case, we auto-activate on opening tags and attributes namespace.
|
||||
*
|
||||
* @return the auto activation characters for completion proposal or <code>null</code>
|
||||
* if no auto activation is desired
|
||||
*/
|
||||
public char[] getCompletionProposalAutoActivationCharacters() {
|
||||
return new char[]{ '<', ':', '=' };
|
||||
}
|
||||
|
||||
public char[] getContextInformationAutoActivationCharacters() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public IContextInformationValidator getContextInformationValidator() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Heuristically extracts the prefix used for determining template relevance
|
||||
* from the viewer's document. The default implementation returns the String from
|
||||
* offset backwards that forms a potential XML element name.
|
||||
*
|
||||
* Code extracted from org.eclipse.jface.text.templatesTemplateCompletionProcessor
|
||||
* and adapted to our needs.
|
||||
*
|
||||
* @param viewer the viewer
|
||||
* @param offset offset into document
|
||||
* @return the prefix to consider
|
||||
*/
|
||||
protected String extractElementPrefix(ITextViewer viewer, int offset) {
|
||||
int i = offset;
|
||||
IDocument document = viewer.getDocument();
|
||||
if (i > document.getLength()) return ""; //$NON-NLS-1$
|
||||
|
||||
try {
|
||||
for (; i > 0; --i) {
|
||||
char ch = document.getChar(i - 1);
|
||||
// accepted characters are a-z and : (for attributes' namespace)
|
||||
if (ch != ':' && (ch < 'a' || ch > 'z')) break;
|
||||
}
|
||||
|
||||
return document.get(i, offset - i);
|
||||
} catch (BadLocationException e) {
|
||||
return ""; //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the character at the given offset.
|
||||
* Returns 0 if the offset is invalid.
|
||||
*/
|
||||
protected char extractChar(ITextViewer viewer, int offset) {
|
||||
IDocument document = viewer.getDocument();
|
||||
if (offset > document.getLength()) return 0;
|
||||
|
||||
try {
|
||||
return document.getChar(offset);
|
||||
} catch (BadLocationException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about the current edit of an attribute as reported by parseAttributeInfo.
|
||||
*/
|
||||
private class AttribInfo {
|
||||
/** True if the cursor is located in an attribute's value, false if in an attribute name */
|
||||
public boolean isInValue = false;
|
||||
/** The attribute name. Null when not set. */
|
||||
public String name = null;
|
||||
/** The attribute value. Null when not set. The value *may* start with a quote
|
||||
* (' or "), in which case we know we don't need to quote the string for the user */
|
||||
public String value = null;
|
||||
/** String typed by the user so far (i.e. right before requesting code completion),
|
||||
* which will be corrected if we find a possible completion for an attribute value.
|
||||
* See the long comment in getChoicesForAttribute(). */
|
||||
public String correctedPrefix = null;
|
||||
/** Non-zero if an attribute value need a start/end tag (i.e. quotes or brackets) */
|
||||
public char needTag = 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Try to guess if the cursor is editing an element's name or an attribute following an
|
||||
* element. If it's an attribute, try to find if an attribute name is being defined or
|
||||
* its value.
|
||||
* <br/>
|
||||
* This is currently *only* called when we know the cursor is after a complete element
|
||||
* tag name, so it should never return null.
|
||||
* <br/>
|
||||
* Reference for XML syntax: http://www.w3.org/TR/2006/REC-xml-20060816/#sec-starttags
|
||||
* <br/>
|
||||
* @return An AttribInfo describing which attribute is being edited or null if the cursor is
|
||||
* not editing an attribute (in which case it must be an element's name).
|
||||
*/
|
||||
private AttribInfo parseAttributeInfo(ITextViewer viewer, int offset) {
|
||||
AttribInfo info = new AttribInfo();
|
||||
|
||||
IDocument document = viewer.getDocument();
|
||||
int n = document.getLength();
|
||||
if (offset <= n) {
|
||||
try {
|
||||
n = offset;
|
||||
for (;offset > 0; --offset) {
|
||||
char ch = document.getChar(offset - 1);
|
||||
if (ch == '<') break;
|
||||
}
|
||||
|
||||
// text will contain the full string of the current element,
|
||||
// i.e. whatever is after the "<" to the current cursor
|
||||
String text = document.get(offset, n - offset);
|
||||
|
||||
// Normalize whitespace to single spaces
|
||||
text = sWhitespace.matcher(text).replaceAll(" "); //$NON-NLS-1$
|
||||
|
||||
// Remove the leading element name. By spec, it must be after the < without
|
||||
// any whitespace. If there's nothing left, no attribute has been defined yet.
|
||||
// Be sure to keep any whitespace after the initial word if any, as it matters.
|
||||
text = sFirstElementWord.matcher(text).replaceFirst(""); //$NON-NLS-1$
|
||||
|
||||
// There MUST be space after the element name. If not, the cursor is still
|
||||
// defining the element name.
|
||||
if (!text.startsWith(" ")) { //$NON-NLS-1$
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remove full attributes:
|
||||
// Syntax:
|
||||
// name = "..." quoted string with all but < and "
|
||||
// or:
|
||||
// name = '...' quoted string with all but < and '
|
||||
String temp;
|
||||
do {
|
||||
temp = text;
|
||||
text = sFirstAttribute.matcher(temp).replaceFirst(""); //$NON-NLS-1$
|
||||
} while(!temp.equals(text));
|
||||
|
||||
// Now we're left with 3 cases:
|
||||
// - nothing: either there is no attribute definition or the cursor located after
|
||||
// a completed attribute definition.
|
||||
// - a string with no =: the user is writing an attribute name. This case can be
|
||||
// merged with the previous one.
|
||||
// - string with an = sign, optionally followed by a quote (' or "): the user is
|
||||
// writing the value of the attribute.
|
||||
int pos_equal = text.indexOf('=');
|
||||
if (pos_equal == -1) {
|
||||
info.isInValue = false;
|
||||
info.name = text.trim();
|
||||
} else {
|
||||
info.isInValue = true;
|
||||
info.name = text.substring(0, pos_equal).trim();
|
||||
info.value = text.substring(pos_equal + 1).trim();
|
||||
}
|
||||
return info;
|
||||
} catch (BadLocationException e) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the XML DOM node corresponding to the given offset of the given document.
|
||||
*/
|
||||
protected Node getNode(ITextViewer viewer, int offset) {
|
||||
Node node = null;
|
||||
try {
|
||||
IModelManager mm = StructuredModelManager.getModelManager();
|
||||
if (mm != null) {
|
||||
IStructuredModel model = mm.getExistingModelForRead(viewer.getDocument());
|
||||
if (model != null) {
|
||||
for(; offset >= 0 && node == null; --offset) {
|
||||
node = (Node) model.getIndexedRegion(offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Ignore exceptions.
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes (if needed) and returns the root descriptor.
|
||||
* @return
|
||||
*/
|
||||
private ElementDescriptor getRootDescriptor() {
|
||||
if (mRootDescriptor == null) {
|
||||
AndroidTargetData data = mEditor.getTargetData();
|
||||
if (data != null) {
|
||||
IDescriptorProvider descriptorProvider = data.getDescriptorProvider(mDescriptorId);
|
||||
|
||||
if (descriptorProvider != null) {
|
||||
mRootDescriptor = new ElementDescriptor("",
|
||||
descriptorProvider.getRootElementDescriptors());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mRootDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the active {@link AndroidEditor} matching this source viewer.
|
||||
*/
|
||||
private AndroidEditor getAndroidEditor(ITextViewer viewer) {
|
||||
IWorkbenchWindow wwin = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
|
||||
if (wwin != null) {
|
||||
IWorkbenchPage page = wwin.getActivePage();
|
||||
if (page != null) {
|
||||
IEditorPart editor = page.getActiveEditor();
|
||||
if (editor instanceof AndroidEditor) {
|
||||
ISourceViewer ssviewer = ((AndroidEditor) editor).getStructuredSourceViewer();
|
||||
if (ssviewer == viewer) {
|
||||
return (AndroidEditor) editor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,820 @@
|
||||
/*
|
||||
* 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.editors;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.sdk.AndroidTargetData;
|
||||
import com.android.ide.eclipse.adt.sdk.Sdk;
|
||||
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
|
||||
import com.android.sdklib.IAndroidTarget;
|
||||
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
import org.eclipse.core.resources.IResourceChangeEvent;
|
||||
import org.eclipse.core.resources.IResourceChangeListener;
|
||||
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.QualifiedName;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.eclipse.jface.action.IAction;
|
||||
import org.eclipse.jface.dialogs.ErrorDialog;
|
||||
import org.eclipse.jface.text.source.ISourceViewer;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
import org.eclipse.ui.IActionBars;
|
||||
import org.eclipse.ui.IEditorInput;
|
||||
import org.eclipse.ui.IEditorPart;
|
||||
import org.eclipse.ui.IEditorSite;
|
||||
import org.eclipse.ui.IFileEditorInput;
|
||||
import org.eclipse.ui.IWorkbenchPage;
|
||||
import org.eclipse.ui.PartInitException;
|
||||
import org.eclipse.ui.actions.ActionFactory;
|
||||
import org.eclipse.ui.browser.IWorkbenchBrowserSupport;
|
||||
import org.eclipse.ui.forms.IManagedForm;
|
||||
import org.eclipse.ui.forms.editor.FormEditor;
|
||||
import org.eclipse.ui.forms.editor.IFormPage;
|
||||
import org.eclipse.ui.forms.events.HyperlinkAdapter;
|
||||
import org.eclipse.ui.forms.events.HyperlinkEvent;
|
||||
import org.eclipse.ui.forms.events.IHyperlinkListener;
|
||||
import org.eclipse.ui.forms.widgets.FormText;
|
||||
import org.eclipse.ui.internal.browser.WorkbenchBrowserSupport;
|
||||
import org.eclipse.ui.part.FileEditorInput;
|
||||
import org.eclipse.ui.part.MultiPageEditorPart;
|
||||
import org.eclipse.ui.part.WorkbenchPart;
|
||||
import org.eclipse.wst.sse.core.StructuredModelManager;
|
||||
import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
|
||||
import org.eclipse.wst.sse.core.internal.provisional.IModelStateListener;
|
||||
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
|
||||
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
|
||||
import org.eclipse.wst.sse.ui.StructuredTextEditor;
|
||||
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Multi-page form editor for Android XML files.
|
||||
* <p/>
|
||||
* It is designed to work with a {@link StructuredTextEditor} that will display an XML file.
|
||||
* <br/>
|
||||
* Derived classes must implement createFormPages to create the forms before the
|
||||
* source editor. This can be a no-op if desired.
|
||||
*/
|
||||
public abstract class AndroidEditor extends FormEditor implements IResourceChangeListener {
|
||||
|
||||
/** Preference name for the current page of this file */
|
||||
private static final String PREF_CURRENT_PAGE = "_current_page";
|
||||
|
||||
/** Id string used to create the Android SDK browser */
|
||||
private static String BROWSER_ID = "android"; // $NON-NLS-1$
|
||||
|
||||
/** Page id of the XML source editor, used for switching tabs programmatically */
|
||||
public final static String TEXT_EDITOR_ID = "editor_part"; //$NON-NLS-1$
|
||||
|
||||
/** Width hint for text fields. Helps the grid layout resize properly on smaller screens */
|
||||
public static final int TEXT_WIDTH_HINT = 50;
|
||||
|
||||
/** Page index of the text editor (always the last page) */
|
||||
private int mTextPageIndex;
|
||||
/** The text editor */
|
||||
private StructuredTextEditor mTextEditor;
|
||||
/** Listener for the XML model from the StructuredEditor */
|
||||
private XmlModelStateListener mXmlModelStateListener;
|
||||
/** Listener to update the root node if the resource framework changes */
|
||||
private Runnable mResourceRefreshListener;
|
||||
|
||||
/**
|
||||
* Creates a form editor.
|
||||
*/
|
||||
public AndroidEditor() {
|
||||
super();
|
||||
ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
|
||||
|
||||
mResourceRefreshListener = new Runnable() {
|
||||
public void run() {
|
||||
commitPages(false /* onSave */);
|
||||
|
||||
// recreate the ui root node always
|
||||
initUiRootNode(true /*force*/);
|
||||
}
|
||||
};
|
||||
AdtPlugin.getDefault().addResourceChangedListener(mResourceRefreshListener);
|
||||
}
|
||||
|
||||
// ---- Abstract Methods ----
|
||||
|
||||
/**
|
||||
* Returns the root node of the UI element hierarchy manipulated by the current
|
||||
* UI node editor.
|
||||
*/
|
||||
abstract public UiElementNode getUiRootNode();
|
||||
|
||||
/**
|
||||
* Creates the various form pages.
|
||||
* <p/>
|
||||
* Derived classes must implement this to add their own specific tabs.
|
||||
*/
|
||||
abstract protected void createFormPages();
|
||||
|
||||
/**
|
||||
* Creates the initial UI Root Node, including the known mandatory elements.
|
||||
* @param force if true, a new UiManifestNode is recreated even if it already exists.
|
||||
*/
|
||||
abstract protected void initUiRootNode(boolean force);
|
||||
|
||||
/**
|
||||
* Subclasses should override this method to process the new XML Model, which XML
|
||||
* root node is given.
|
||||
*
|
||||
* The base implementation is empty.
|
||||
*
|
||||
* @param xml_doc The XML document, if available, or null if none exists.
|
||||
*/
|
||||
protected void xmlModelChanged(Document xml_doc) {
|
||||
// pass
|
||||
}
|
||||
|
||||
// ---- Base Class Overrides, Interfaces Implemented ----
|
||||
|
||||
/**
|
||||
* Creates the pages of the multi-page editor.
|
||||
*/
|
||||
@Override
|
||||
protected void addPages() {
|
||||
createAndroidPages();
|
||||
selectDefaultPage(null /* defaultPageId */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the page for the Android Editors
|
||||
*/
|
||||
protected void createAndroidPages() {
|
||||
createFormPages();
|
||||
createTextEditor();
|
||||
|
||||
createUndoRedoActions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates undo redo actions for the editor site (so that it works for any page of this
|
||||
* multi-page editor) by re-using the actions defined by the {@link StructuredTextEditor}
|
||||
* (aka the XML text editor.)
|
||||
*/
|
||||
private void createUndoRedoActions() {
|
||||
IActionBars bars = getEditorSite().getActionBars();
|
||||
if (bars != null) {
|
||||
IAction action = mTextEditor.getAction(ActionFactory.UNDO.getId());
|
||||
bars.setGlobalActionHandler(ActionFactory.UNDO.getId(), action);
|
||||
|
||||
action = mTextEditor.getAction(ActionFactory.REDO.getId());
|
||||
bars.setGlobalActionHandler(ActionFactory.REDO.getId(), action);
|
||||
|
||||
bars.updateActionBars();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the default active page.
|
||||
* @param defaultPageId the id of the page to show. If <code>null</code> the editor attempts to
|
||||
* find the default page in the properties of the {@link IResource} object being edited.
|
||||
*/
|
||||
protected void selectDefaultPage(String defaultPageId) {
|
||||
if (defaultPageId == null) {
|
||||
if (getEditorInput() instanceof IFileEditorInput) {
|
||||
IFile file = ((IFileEditorInput) getEditorInput()).getFile();
|
||||
|
||||
QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID,
|
||||
getClass().getSimpleName() + PREF_CURRENT_PAGE);
|
||||
String pageId;
|
||||
try {
|
||||
pageId = file.getPersistentProperty(qname);
|
||||
if (pageId != null) {
|
||||
defaultPageId = pageId;
|
||||
}
|
||||
} catch (CoreException e) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultPageId != null) {
|
||||
try {
|
||||
setActivePage(Integer.parseInt(defaultPageId));
|
||||
} catch (Exception e) {
|
||||
// We can get NumberFormatException from parseInt but also
|
||||
// AssertionError from setActivePage when the index is out of bounds.
|
||||
// Generally speaking we just want to ignore any exception and fall back on the
|
||||
// first page rather than crash the editor load. Logging the error is enough.
|
||||
AdtPlugin.log(e, "Selecting page '%s' in AndroidEditor failed", defaultPageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all the pages from the editor.
|
||||
*/
|
||||
protected void removePages() {
|
||||
int count = getPageCount();
|
||||
for (int i = count - 1 ; i >= 0 ; i--) {
|
||||
removePage(i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the parent's setActivePage to be able to switch to the xml editor.
|
||||
*
|
||||
* If the special pageId TEXT_EDITOR_ID is given, switches to the mTextPageIndex page.
|
||||
* This is needed because the editor doesn't actually derive from IFormPage and thus
|
||||
* doesn't have the get-by-page-id method. In this case, the method returns null since
|
||||
* IEditorPart does not implement IFormPage.
|
||||
*/
|
||||
@Override
|
||||
public IFormPage setActivePage(String pageId) {
|
||||
if (pageId.equals(TEXT_EDITOR_ID)) {
|
||||
super.setActivePage(mTextPageIndex);
|
||||
return null;
|
||||
} else {
|
||||
return super.setActivePage(pageId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Notifies this multi-page editor that the page with the given id has been
|
||||
* activated. This method is called when the user selects a different tab.
|
||||
*
|
||||
* @see MultiPageEditorPart#pageChange(int)
|
||||
*/
|
||||
@Override
|
||||
protected void pageChange(int newPageIndex) {
|
||||
super.pageChange(newPageIndex);
|
||||
|
||||
if (getEditorInput() instanceof IFileEditorInput) {
|
||||
IFile file = ((IFileEditorInput) getEditorInput()).getFile();
|
||||
|
||||
QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID,
|
||||
getClass().getSimpleName() + PREF_CURRENT_PAGE);
|
||||
try {
|
||||
file.setPersistentProperty(qname, Integer.toString(newPageIndex));
|
||||
} catch (CoreException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies this listener that some resource changes
|
||||
* are happening, or have already happened.
|
||||
*
|
||||
* Closes all project files on project close.
|
||||
* @see IResourceChangeListener
|
||||
*/
|
||||
public void resourceChanged(final IResourceChangeEvent event) {
|
||||
if (event.getType() == IResourceChangeEvent.PRE_CLOSE) {
|
||||
Display.getDefault().asyncExec(new Runnable() {
|
||||
public void run() {
|
||||
IWorkbenchPage[] pages = getSite().getWorkbenchWindow()
|
||||
.getPages();
|
||||
for (int i = 0; i < pages.length; i++) {
|
||||
if (((FileEditorInput)mTextEditor.getEditorInput())
|
||||
.getFile().getProject().equals(
|
||||
event.getResource())) {
|
||||
IEditorPart editorPart = pages[i].findEditor(mTextEditor
|
||||
.getEditorInput());
|
||||
pages[i].closeEditor(editorPart, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the editor part with a site and input.
|
||||
* <p/>
|
||||
* Checks that the input is an instance of {@link IFileEditorInput}.
|
||||
*
|
||||
* @see FormEditor
|
||||
*/
|
||||
@Override
|
||||
public void init(IEditorSite site, IEditorInput editorInput) throws PartInitException {
|
||||
if (!(editorInput instanceof IFileEditorInput))
|
||||
throw new PartInitException("Invalid Input: Must be IFileEditorInput");
|
||||
super.init(site, editorInput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes attached listeners.
|
||||
*
|
||||
* @see WorkbenchPart
|
||||
*/
|
||||
@Override
|
||||
public void dispose() {
|
||||
IStructuredModel xml_model = getModelForRead();
|
||||
if (xml_model != null) {
|
||||
try {
|
||||
if (mXmlModelStateListener != null) {
|
||||
xml_model.removeModelStateListener(mXmlModelStateListener);
|
||||
}
|
||||
|
||||
} finally {
|
||||
xml_model.releaseFromRead();
|
||||
}
|
||||
}
|
||||
ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
|
||||
|
||||
if (mResourceRefreshListener != null) {
|
||||
AdtPlugin.getDefault().removeResourceChangedListener(mResourceRefreshListener);
|
||||
mResourceRefreshListener = null;
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit all dirty pages then saves the contents of the text editor.
|
||||
* <p/>
|
||||
* This works by committing all data to the XML model and then
|
||||
* asking the Structured XML Editor to save the XML.
|
||||
*
|
||||
* @see IEditorPart
|
||||
*/
|
||||
@Override
|
||||
public void doSave(IProgressMonitor monitor) {
|
||||
commitPages(true /* onSave */);
|
||||
|
||||
// The actual "save" operation is done by the Structured XML Editor
|
||||
getEditor(mTextPageIndex).doSave(monitor);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* Saves the contents of this editor to another object.
|
||||
* <p>
|
||||
* Subclasses must override this method to implement the open-save-close lifecycle
|
||||
* for an editor. For greater details, see <code>IEditorPart</code>
|
||||
* </p>
|
||||
*
|
||||
* @see IEditorPart
|
||||
*/
|
||||
@Override
|
||||
public void doSaveAs() {
|
||||
commitPages(true /* onSave */);
|
||||
|
||||
IEditorPart editor = getEditor(mTextPageIndex);
|
||||
editor.doSaveAs();
|
||||
setPageText(mTextPageIndex, editor.getTitle());
|
||||
setInput(editor.getEditorInput());
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits all dirty pages in the editor. This method should
|
||||
* be called as a first step of a 'save' operation.
|
||||
* <p/>
|
||||
* This is the same implementation as in {@link FormEditor}
|
||||
* except it fixes two bugs: a cast to IFormPage is done
|
||||
* from page.get(i) <em>before</em> being tested with instanceof.
|
||||
* Another bug is that the last page might be a null pointer.
|
||||
* <p/>
|
||||
* The incorrect casting makes the original implementation crash due
|
||||
* to our {@link StructuredTextEditor} not being an {@link IFormPage}
|
||||
* so we have to override and duplicate to fix it.
|
||||
*
|
||||
* @param onSave <code>true</code> if commit is performed as part
|
||||
* of the 'save' operation, <code>false</code> otherwise.
|
||||
* @since 3.3
|
||||
*/
|
||||
@Override
|
||||
public void commitPages(boolean onSave) {
|
||||
if (pages != null) {
|
||||
for (int i = 0; i < pages.size(); i++) {
|
||||
Object page = pages.get(i);
|
||||
if (page != null && page instanceof IFormPage) {
|
||||
IFormPage form_page = (IFormPage) page;
|
||||
IManagedForm managed_form = form_page.getManagedForm();
|
||||
if (managed_form != null && managed_form.isDirty()) {
|
||||
managed_form.commit(onSave);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* Returns whether the "save as" operation is supported by this editor.
|
||||
* <p>
|
||||
* Subclasses must override this method to implement the open-save-close lifecycle
|
||||
* for an editor. For greater details, see <code>IEditorPart</code>
|
||||
* </p>
|
||||
*
|
||||
* @see IEditorPart
|
||||
*/
|
||||
@Override
|
||||
public boolean isSaveAsAllowed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- Local methods ----
|
||||
|
||||
|
||||
/**
|
||||
* Helper method that creates a new hyper-link Listener.
|
||||
* Used by derived classes which need active links in {@link FormText}.
|
||||
* <p/>
|
||||
* This link listener handles two kinds of URLs:
|
||||
* <ul>
|
||||
* <li> Links starting with "http" are simply sent to a local browser.
|
||||
* <li> Links starting with "file:/" are simply sent to a local browser.
|
||||
* <li> Links starting with "page:" are expected to be an editor page id to switch to.
|
||||
* <li> Other links are ignored.
|
||||
* </ul>
|
||||
*
|
||||
* @return A new hyper-link listener for FormText to use.
|
||||
*/
|
||||
public final IHyperlinkListener createHyperlinkListener() {
|
||||
return new HyperlinkAdapter() {
|
||||
/**
|
||||
* Switch to the page corresponding to the link that has just been clicked.
|
||||
* For this purpose, the HREF of the <a> tags above is the page ID to switch to.
|
||||
*/
|
||||
@Override
|
||||
public void linkActivated(HyperlinkEvent e) {
|
||||
super.linkActivated(e);
|
||||
String link = e.data.toString();
|
||||
if (link.startsWith("http") || //$NON-NLS-1$
|
||||
link.startsWith("file:/")) { //$NON-NLS-1$
|
||||
openLinkInBrowser(link);
|
||||
} else if (link.startsWith("page:")) { //$NON-NLS-1$
|
||||
// Switch to an internal page
|
||||
setActivePage(link.substring(5 /* strlen("page:") */));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the http link into a browser
|
||||
*
|
||||
* @param link The URL to open in a browser
|
||||
*/
|
||||
private void openLinkInBrowser(String link) {
|
||||
try {
|
||||
IWorkbenchBrowserSupport wbs = WorkbenchBrowserSupport.getInstance();
|
||||
wbs.createBrowser(BROWSER_ID).openURL(new URL(link));
|
||||
} catch (PartInitException e1) {
|
||||
// pass
|
||||
} catch (MalformedURLException e1) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the XML source editor.
|
||||
* <p/>
|
||||
* Memorizes the index page of the source editor (it's always the last page, but the number
|
||||
* of pages before can change.)
|
||||
* <br/>
|
||||
* Retrieves the underlying XML model from the StructuredEditor and attaches a listener to it.
|
||||
* Finally triggers modelChanged() on the model listener -- derived classes can use this
|
||||
* to initialize the model the first time.
|
||||
* <p/>
|
||||
* Called only once <em>after</em> createFormPages.
|
||||
*/
|
||||
private void createTextEditor() {
|
||||
try {
|
||||
mTextEditor = new StructuredTextEditor();
|
||||
int index = addPage(mTextEditor, getEditorInput());
|
||||
mTextPageIndex = index;
|
||||
setPageText(index, mTextEditor.getTitle());
|
||||
|
||||
if (!(mTextEditor.getTextViewer().getDocument() instanceof IStructuredDocument)) {
|
||||
Status status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
|
||||
"Error opening the Android XML editor. Is the document an XML file?");
|
||||
throw new RuntimeException("Android XML Editor Error", new CoreException(status));
|
||||
}
|
||||
|
||||
IStructuredModel xml_model = getModelForRead();
|
||||
if (xml_model != null) {
|
||||
try {
|
||||
mXmlModelStateListener = new XmlModelStateListener();
|
||||
xml_model.addModelStateListener(mXmlModelStateListener);
|
||||
mXmlModelStateListener.modelChanged(xml_model);
|
||||
} catch (Exception e) {
|
||||
AdtPlugin.log(e, "Error while loading editor"); //$NON-NLS-1$
|
||||
} finally {
|
||||
xml_model.releaseFromRead();
|
||||
}
|
||||
}
|
||||
} catch (PartInitException e) {
|
||||
ErrorDialog.openError(getSite().getShell(),
|
||||
"Android XML Editor Error", null, e.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ISourceViewer associated with the Structured Text editor.
|
||||
*/
|
||||
public final ISourceViewer getStructuredSourceViewer() {
|
||||
if (mTextEditor != null) {
|
||||
// We can't access mEditor.getSourceViewer() because it is protected,
|
||||
// however getTextViewer simply returns the SourceViewer casted, so we
|
||||
// can use it instead.
|
||||
return mTextEditor.getTextViewer();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link IStructuredDocument} used by the StructuredTextEditor (aka Source
|
||||
* Editor) or null if not available.
|
||||
*/
|
||||
public final IStructuredDocument getStructuredDocument() {
|
||||
if (mTextEditor != null && mTextEditor.getTextViewer() != null) {
|
||||
return (IStructuredDocument) mTextEditor.getTextViewer().getDocument();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a version of the model that has been shared for read.
|
||||
* <p/>
|
||||
* Callers <em>must</em> call model.releaseFromRead() when done, typically
|
||||
* in a try..finally clause.
|
||||
*
|
||||
* @return The model for the XML document or null if cannot be obtained from the editor
|
||||
*/
|
||||
public final IStructuredModel getModelForRead() {
|
||||
IStructuredDocument document = getStructuredDocument();
|
||||
if (document != null) {
|
||||
IModelManager mm = StructuredModelManager.getModelManager();
|
||||
if (mm != null) {
|
||||
return mm.getModelForRead(document);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a version of the model that has been shared for edit.
|
||||
* <p/>
|
||||
* Callers <em>must</em> call model.releaseFromEdit() when done, typically
|
||||
* in a try..finally clause.
|
||||
*
|
||||
* @return The model for the XML document or null if cannot be obtained from the editor
|
||||
*/
|
||||
public final IStructuredModel getModelForEdit() {
|
||||
IStructuredDocument document = getStructuredDocument();
|
||||
if (document != null) {
|
||||
IModelManager mm = StructuredModelManager.getModelManager();
|
||||
if (mm != null) {
|
||||
return mm.getModelForEdit(document);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to perform edits on the XML model whilst making sure the
|
||||
* model has been prepared to be changed.
|
||||
* <p/>
|
||||
* It first gets a model for edition using {@link #getModelForEdit()},
|
||||
* then calls {@link IStructuredModel#aboutToChangeModel()},
|
||||
* then performs the requested action
|
||||
* and finally calls {@link IStructuredModel#changedModel()}
|
||||
* and {@link IStructuredModel#releaseFromEdit()}.
|
||||
* <p/>
|
||||
* The method is synchronous. As soon as the {@link IStructuredModel#changedModel()} method
|
||||
* is called, XML model listeners will be triggered.
|
||||
*
|
||||
* @param edit_action Something that will change the XML.
|
||||
*/
|
||||
public final void editXmlModel(Runnable edit_action) {
|
||||
IStructuredModel model = getModelForEdit();
|
||||
try {
|
||||
model.aboutToChangeModel();
|
||||
edit_action.run();
|
||||
} finally {
|
||||
// Notify the model we're done modifying it. This must *always* be executed.
|
||||
model.changedModel();
|
||||
model.releaseFromEdit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an "undo recording" session. This is managed by the underlying undo manager
|
||||
* associated to the structured XML model.
|
||||
* <p/>
|
||||
* There <em>must</em> be a corresponding call to {@link #endUndoRecording()}.
|
||||
* <p/>
|
||||
* beginUndoRecording/endUndoRecording calls can be nested (inner calls are ignored, only one
|
||||
* undo operation is recorded.)
|
||||
*
|
||||
* @param label The label for the undo operation. Can be null but we should really try to put
|
||||
* something meaningful if possible.
|
||||
* @return True if the undo recording actually started, false if any kind of error occured.
|
||||
* {@link #endUndoRecording()} should only be called if True is returned.
|
||||
*/
|
||||
private final boolean beginUndoRecording(String label) {
|
||||
IStructuredDocument document = getStructuredDocument();
|
||||
if (document != null) {
|
||||
IModelManager mm = StructuredModelManager.getModelManager();
|
||||
if (mm != null) {
|
||||
IStructuredModel model = mm.getModelForEdit(document);
|
||||
if (model != null) {
|
||||
model.beginRecording(this, label);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends an "undo recording" session.
|
||||
* <p/>
|
||||
* This is the counterpart call to {@link #beginUndoRecording(String)} and should only be
|
||||
* used if the initial call returned true.
|
||||
*/
|
||||
private final void endUndoRecording() {
|
||||
IStructuredDocument document = getStructuredDocument();
|
||||
if (document != null) {
|
||||
IModelManager mm = StructuredModelManager.getModelManager();
|
||||
if (mm != null) {
|
||||
IStructuredModel model = mm.getModelForEdit(document);
|
||||
if (model != null) {
|
||||
model.endRecording(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an "undo recording" session by calling the undoableAction runnable
|
||||
* using {@link #beginUndoRecording(String)} and {@link #endUndoRecording()}.
|
||||
* <p>
|
||||
* You can nest several calls to {@link #wrapUndoRecording(String, Runnable)}, only one
|
||||
* recording session will be created.
|
||||
*
|
||||
* @param label The label for the undo operation. Can be null. Ideally we should really try
|
||||
* to put something meaningful if possible.
|
||||
*/
|
||||
public void wrapUndoRecording(String label, Runnable undoableAction) {
|
||||
boolean recording = false;
|
||||
try {
|
||||
recording = beginUndoRecording(label);
|
||||
undoableAction.run();
|
||||
} finally {
|
||||
if (recording) {
|
||||
endUndoRecording();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the XML {@link Document} or null if we can't get it
|
||||
*/
|
||||
protected final Document getXmlDocument(IStructuredModel model) {
|
||||
if (model == null) {
|
||||
AdtPlugin.log(IStatus.WARNING, "Android Editor: No XML model for root node."); //$NON-NLS-1$
|
||||
return null;
|
||||
}
|
||||
|
||||
if (model instanceof IDOMModel) {
|
||||
IDOMModel dom_model = (IDOMModel) model;
|
||||
return dom_model.getDocument();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link IProject} for the edited file.
|
||||
*/
|
||||
public IProject getProject() {
|
||||
if (mTextEditor != null) {
|
||||
IEditorInput input = mTextEditor.getEditorInput();
|
||||
if (input instanceof FileEditorInput) {
|
||||
FileEditorInput fileInput = (FileEditorInput)input;
|
||||
IFile inputFile = fileInput.getFile();
|
||||
|
||||
if (inputFile != null) {
|
||||
return inputFile.getProject();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link PlatformData} for the edited file.
|
||||
*/
|
||||
public AndroidTargetData getTargetData() {
|
||||
IProject project = getProject();
|
||||
if (project != null) {
|
||||
Sdk currentSdk = Sdk.getCurrent();
|
||||
if (currentSdk != null) {
|
||||
IAndroidTarget target = currentSdk.getTarget(project);
|
||||
|
||||
if (target != null) {
|
||||
return currentSdk.getTargetData(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Listen to changes in the underlying XML model in the structured editor.
|
||||
*/
|
||||
private class XmlModelStateListener implements IModelStateListener {
|
||||
|
||||
/**
|
||||
* A model is about to be changed. This typically is initiated by one
|
||||
* client of the model, to signal a large change and/or a change to the
|
||||
* model's ID or base Location. A typical use might be if a client might
|
||||
* want to suspend processing until all changes have been made.
|
||||
* <p/>
|
||||
* This AndroidEditor implementation of IModelChangedListener is empty.
|
||||
*/
|
||||
public void modelAboutToBeChanged(IStructuredModel model) {
|
||||
// pass
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that the changes foretold by modelAboutToBeChanged have been
|
||||
* made. A typical use might be to refresh, or to resume processing that
|
||||
* was suspended as a result of modelAboutToBeChanged.
|
||||
* <p/>
|
||||
* This AndroidEditor implementation calls the xmlModelChanged callback.
|
||||
*/
|
||||
public void modelChanged(IStructuredModel model) {
|
||||
xmlModelChanged(getXmlDocument(model));
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies that a model's dirty state has changed, and passes that state
|
||||
* in isDirty. A model becomes dirty when any change is made, and becomes
|
||||
* not-dirty when the model is saved.
|
||||
* <p/>
|
||||
* This AndroidEditor implementation of IModelChangedListener is empty.
|
||||
*/
|
||||
public void modelDirtyStateChanged(IStructuredModel model, boolean isDirty) {
|
||||
// pass
|
||||
}
|
||||
|
||||
/**
|
||||
* A modelDeleted means the underlying resource has been deleted. The
|
||||
* model itself is not removed from model management until all have
|
||||
* released it. Note: baseLocation is not (necessarily) changed in this
|
||||
* event, but may not be accurate.
|
||||
* <p/>
|
||||
* This AndroidEditor implementation of IModelChangedListener is empty.
|
||||
*/
|
||||
public void modelResourceDeleted(IStructuredModel model) {
|
||||
// pass
|
||||
}
|
||||
|
||||
/**
|
||||
* A model has been renamed or copied (as in saveAs..). In the renamed
|
||||
* case, the two paramenters are the same instance, and only contain the
|
||||
* new info for id and base location.
|
||||
* <p/>
|
||||
* This AndroidEditor implementation of IModelChangedListener is empty.
|
||||
*/
|
||||
public void modelResourceMoved(IStructuredModel oldModel, IStructuredModel newModel) {
|
||||
// pass
|
||||
}
|
||||
|
||||
/**
|
||||
* This AndroidEditor implementation of IModelChangedListener is empty.
|
||||
*/
|
||||
public void modelAboutToBeReinitialized(IStructuredModel structuredModel) {
|
||||
// pass
|
||||
}
|
||||
|
||||
/**
|
||||
* This AndroidEditor implementation of IModelChangedListener is empty.
|
||||
*/
|
||||
public void modelReinitialized(IStructuredModel structuredModel) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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.editors;
|
||||
|
||||
|
||||
import org.eclipse.jface.text.IAutoEditStrategy;
|
||||
import org.eclipse.jface.text.IDocument;
|
||||
import org.eclipse.jface.text.ITextHover;
|
||||
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
|
||||
import org.eclipse.jface.text.contentassist.IContentAssistant;
|
||||
import org.eclipse.jface.text.formatter.IContentFormatter;
|
||||
import org.eclipse.jface.text.source.ISourceViewer;
|
||||
import org.eclipse.jface.viewers.IInputProvider;
|
||||
import org.eclipse.wst.sse.core.text.IStructuredPartitions;
|
||||
import org.eclipse.wst.xml.core.text.IXMLPartitions;
|
||||
import org.eclipse.wst.xml.ui.StructuredTextViewerConfigurationXML;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Base Source Viewer Configuration for Android resources.
|
||||
*/
|
||||
public class AndroidSourceViewerConfig extends StructuredTextViewerConfigurationXML {
|
||||
|
||||
/** Content Assist Processor to use for all handled partitions. */
|
||||
private IContentAssistProcessor mProcessor;
|
||||
|
||||
public AndroidSourceViewerConfig(IContentAssistProcessor processor) {
|
||||
super();
|
||||
mProcessor = processor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) {
|
||||
return super.getContentAssistant(sourceViewer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content assist processors that will be used for content
|
||||
* assist in the given source viewer and for the given partition type.
|
||||
*
|
||||
* @param sourceViewer the source viewer to be configured by this
|
||||
* configuration
|
||||
* @param partitionType the partition type for which the content assist
|
||||
* processors are applicable
|
||||
* @return IContentAssistProcessors or null if should not be supported
|
||||
*/
|
||||
@Override
|
||||
protected IContentAssistProcessor[] getContentAssistProcessors(
|
||||
ISourceViewer sourceViewer, String partitionType) {
|
||||
ArrayList<IContentAssistProcessor> processors = new ArrayList<IContentAssistProcessor>();
|
||||
if (partitionType == IStructuredPartitions.UNKNOWN_PARTITION ||
|
||||
partitionType == IStructuredPartitions.DEFAULT_PARTITION ||
|
||||
partitionType == IXMLPartitions.XML_DEFAULT) {
|
||||
if (sourceViewer instanceof IInputProvider) {
|
||||
IInputProvider input = (IInputProvider) sourceViewer;
|
||||
Object a = input.getInput();
|
||||
if (a != null)
|
||||
a.toString();
|
||||
}
|
||||
|
||||
IDocument doc = sourceViewer.getDocument();
|
||||
if (doc != null)
|
||||
doc.toString();
|
||||
|
||||
processors.add(mProcessor);
|
||||
}
|
||||
|
||||
IContentAssistProcessor[] others = super.getContentAssistProcessors(sourceViewer,
|
||||
partitionType);
|
||||
if (others != null && others.length > 0) {
|
||||
for (IContentAssistProcessor p : others) {
|
||||
processors.add(p);
|
||||
}
|
||||
}
|
||||
|
||||
if (processors.size() > 0) {
|
||||
return processors.toArray(new IContentAssistProcessor[processors.size()]);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITextHover getTextHover(ISourceViewer sourceViewer, String contentType) {
|
||||
// TODO text hover for android xml
|
||||
return super.getTextHover(sourceViewer, contentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAutoEditStrategy[] getAutoEditStrategies(
|
||||
ISourceViewer sourceViewer, String contentType) {
|
||||
// TODO auto edit strategies for android xml
|
||||
return super.getAutoEditStrategies(sourceViewer, contentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IContentFormatter getContentFormatter(ISourceViewer sourceViewer) {
|
||||
// TODO content formatter for android xml
|
||||
return super.getContentFormatter(sourceViewer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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.editors;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
|
||||
/**
|
||||
* Quickly parses a (potential) XML file to extract its first element (i.e. the root element)
|
||||
* and namespace, if any.
|
||||
* <p/>
|
||||
* This is used to determine if a file is an XML document that the XmlEditor can process.
|
||||
* <p/>
|
||||
* TODO use this to remove the hardcoded "android" namespace prefix limitation.
|
||||
*/
|
||||
public final class FirstElementParser {
|
||||
|
||||
private static SAXParserFactory sSaxfactory;
|
||||
|
||||
/**
|
||||
* Result from the XML parsing. <br/>
|
||||
* Contains the name of the root XML element. <br/>
|
||||
* If an XMLNS URI was specified and found, the XMLNS prefix is recorded. Otherwise it is null.
|
||||
*/
|
||||
public static final class Result {
|
||||
private String mElement;
|
||||
private String mXmlnsPrefix;
|
||||
private String mXmlnsUri;
|
||||
|
||||
public String getElement() {
|
||||
return mElement;
|
||||
}
|
||||
|
||||
public String getXmlnsPrefix() {
|
||||
return mXmlnsPrefix;
|
||||
}
|
||||
|
||||
public String getXmlnsUri() {
|
||||
return mXmlnsUri;
|
||||
}
|
||||
|
||||
void setElement(String element) {
|
||||
mElement = element;
|
||||
}
|
||||
|
||||
void setXmlnsPrefix(String xmlnsPrefix) {
|
||||
mXmlnsPrefix = xmlnsPrefix;
|
||||
}
|
||||
|
||||
void setXmlnsUri(String xmlnsUri) {
|
||||
mXmlnsUri = xmlnsUri;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ResultFoundException extends SAXException { }
|
||||
|
||||
/**
|
||||
* Parses the given filename.
|
||||
*
|
||||
* @param osFilename The file to parse.
|
||||
* @param xmlnsUri An optional URL of which we want to know the prefix.
|
||||
* @return The element details found or null if not found.
|
||||
*/
|
||||
public static Result parse(String osFilename, String xmlnsUri) {
|
||||
if (sSaxfactory == null) {
|
||||
// TODO just create a single factory in CommonPlugin and reuse it
|
||||
sSaxfactory = SAXParserFactory.newInstance();
|
||||
sSaxfactory.setNamespaceAware(true);
|
||||
}
|
||||
|
||||
Result result = new Result();
|
||||
if (xmlnsUri != null && xmlnsUri.length() > 0) {
|
||||
result.setXmlnsUri(xmlnsUri);
|
||||
}
|
||||
|
||||
try {
|
||||
SAXParser parser = sSaxfactory.newSAXParser();
|
||||
XmlHandler handler = new XmlHandler(result);
|
||||
parser.parse(new InputSource(new FileReader(osFilename)), handler);
|
||||
|
||||
} catch(ResultFoundException e) {
|
||||
// XML handling was aborted because the required element was found.
|
||||
// Simply return the result.
|
||||
return result;
|
||||
} catch (ParserConfigurationException e) {
|
||||
} catch (SAXException e) {
|
||||
} catch (FileNotFoundException e) {
|
||||
} catch (IOException e) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor. Use the static parse() method instead.
|
||||
*/
|
||||
private FirstElementParser() {
|
||||
// pass
|
||||
}
|
||||
|
||||
/**
|
||||
* A specialized SAX handler that captures the arguments of the very first element
|
||||
* (i.e. the root element)
|
||||
*/
|
||||
private static class XmlHandler extends DefaultHandler {
|
||||
private final Result mResult;
|
||||
|
||||
public XmlHandler(Result result) {
|
||||
mResult = result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a namespace prefix mapping.
|
||||
* I.e. for xmlns:android="some-uri", this received prefix="android" and uri="some-uri".
|
||||
* <p/>
|
||||
* The prefix is recorded in the result structure if the URI is the one searched for.
|
||||
* <p/>
|
||||
* This event happens <em>before</em> the corresponding startElement event.
|
||||
*/
|
||||
@Override
|
||||
public void startPrefixMapping(String prefix, String uri) {
|
||||
if (uri.equals(mResult.getXmlnsUri())) {
|
||||
mResult.setXmlnsPrefix(prefix);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a new element start.
|
||||
* <p/>
|
||||
* This simply records the element name and abort processing by throwing an exception.
|
||||
*/
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String name, Attributes attributes)
|
||||
throws SAXException {
|
||||
mResult.setElement(localName);
|
||||
throw new ResultFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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.editors;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
|
||||
import org.eclipse.jface.resource.ImageDescriptor;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.graphics.Font;
|
||||
import org.eclipse.swt.graphics.FontData;
|
||||
import org.eclipse.swt.graphics.GC;
|
||||
import org.eclipse.swt.graphics.Image;
|
||||
import org.eclipse.swt.graphics.ImageData;
|
||||
import org.eclipse.swt.graphics.Point;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Factory to generate icons for Android Editors.
|
||||
* <p/>
|
||||
* Icons are kept here and reused.
|
||||
*/
|
||||
public class IconFactory {
|
||||
|
||||
public static final int COLOR_RED = SWT.COLOR_DARK_RED;
|
||||
public static final int COLOR_GREEN = SWT.COLOR_DARK_GREEN;
|
||||
public static final int COLOR_BLUE = SWT.COLOR_DARK_BLUE;
|
||||
public static final int COLOR_DEFAULT = SWT.COLOR_BLACK;
|
||||
|
||||
public static final int SHAPE_CIRCLE = 'C';
|
||||
public static final int SHAPE_RECT = 'R';
|
||||
public static final int SHAPE_DEFAULT = SHAPE_CIRCLE;
|
||||
|
||||
private static IconFactory sInstance;
|
||||
|
||||
private HashMap<String, Image> mIconMap = new HashMap<String, Image>();
|
||||
private HashMap<String, ImageDescriptor> mImageDescMap = new HashMap<String, ImageDescriptor>();
|
||||
|
||||
private IconFactory() {
|
||||
}
|
||||
|
||||
public static synchronized IconFactory getInstance() {
|
||||
if (sInstance == null) {
|
||||
sInstance = new IconFactory();
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
// Dispose icons
|
||||
for (Image icon : mIconMap.values()) {
|
||||
// The map can contain null values
|
||||
if (icon != null) {
|
||||
icon.dispose();
|
||||
}
|
||||
}
|
||||
mIconMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Image for a given icon name.
|
||||
* <p/>
|
||||
* Callers should not dispose it.
|
||||
*
|
||||
* @param osName The leaf name, without the extension, of an existing icon in the
|
||||
* editor's "icons" directory. If it doesn't exists, a default icon will be
|
||||
* generated automatically based on the name.
|
||||
*/
|
||||
public Image getIcon(String osName) {
|
||||
return getIcon(osName, COLOR_DEFAULT, SHAPE_DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Image for a given icon name.
|
||||
* <p/>
|
||||
* Callers should not dispose it.
|
||||
*
|
||||
* @param osName The leaf name, without the extension, of an existing icon in the
|
||||
* editor's "icons" directory. If it doesn't exists, a default icon will be
|
||||
* generated automatically based on the name.
|
||||
* @param color The color of the text in the automatically generated icons,
|
||||
* one of COLOR_DEFAULT, COLOR_RED, COLOR_BLUE or COLOR_RED.
|
||||
* @param shape The shape of the icon in the automatically generated icons,
|
||||
* one of SHAPE_DEFAULT, SHAPE_CIRCLE or SHAPE_RECT.
|
||||
*/
|
||||
public Image getIcon(String osName, int color, int shape) {
|
||||
String key = Character.toString((char) shape) + Integer.toString(color) + osName;
|
||||
Image icon = mIconMap.get(key);
|
||||
if (icon == null && !mIconMap.containsKey(key)) {
|
||||
ImageDescriptor id = getImageDescriptor(osName, color, shape);
|
||||
if (id != null) {
|
||||
icon = id.createImage();
|
||||
}
|
||||
// Note that we store null references in the icon map, to avoid looking them
|
||||
// up every time. If it didn't exist once, it will not exist later.
|
||||
mIconMap.put(key, icon);
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ImageDescriptor for a given icon name.
|
||||
* <p/>
|
||||
* Callers should not dispose it.
|
||||
*
|
||||
* @param osName The leaf name, without the extension, of an existing icon in the
|
||||
* editor's "icons" directory. If it doesn't exists, a default icon will be
|
||||
* generated automatically based on the name.
|
||||
*/
|
||||
public ImageDescriptor getImageDescriptor(String osName) {
|
||||
return getImageDescriptor(osName, COLOR_DEFAULT, SHAPE_DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ImageDescriptor for a given icon name.
|
||||
* <p/>
|
||||
* Callers should not dispose it.
|
||||
*
|
||||
* @param osName The leaf name, without the extension, of an existing icon in the
|
||||
* editor's "icons" directory. If it doesn't exists, a default icon will be
|
||||
* generated automatically based on the name.
|
||||
* @param color The color of the text in the automatically generated icons.
|
||||
* one of COLOR_DEFAULT, COLOR_RED, COLOR_BLUE or COLOR_RED.
|
||||
* @param shape The shape of the icon in the automatically generated icons,
|
||||
* one of SHAPE_DEFAULT, SHAPE_CIRCLE or SHAPE_RECT.
|
||||
*/
|
||||
public ImageDescriptor getImageDescriptor(String osName, int color, int shape) {
|
||||
String key = Character.toString((char) shape) + Integer.toString(color) + osName;
|
||||
ImageDescriptor id = mImageDescMap.get(key);
|
||||
if (id == null && !mImageDescMap.containsKey(key)) {
|
||||
id = AdtPlugin.imageDescriptorFromPlugin(
|
||||
AdtPlugin.PLUGIN_ID,
|
||||
String.format("/icons/%1$s.png", osName)); //$NON-NLS-1$
|
||||
|
||||
if (id == null) {
|
||||
id = new LetterImageDescriptor(osName.charAt(0), color, shape);
|
||||
}
|
||||
|
||||
// Note that we store null references in the icon map, to avoid looking them
|
||||
// up every time. If it didn't exist once, it will not exist later.
|
||||
mImageDescMap.put(key, id);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple image description that generates a 16x16 image which consists
|
||||
* of a colored letter inside a black & white circle.
|
||||
*/
|
||||
private static class LetterImageDescriptor extends ImageDescriptor {
|
||||
|
||||
private final char mLetter;
|
||||
private final int mColor;
|
||||
private final int mShape;
|
||||
|
||||
public LetterImageDescriptor(char letter, int color, int shape) {
|
||||
mLetter = letter;
|
||||
mColor = color;
|
||||
mShape = shape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageData getImageData() {
|
||||
|
||||
final int SX = 15;
|
||||
final int SY = 15;
|
||||
final int RX = 4;
|
||||
final int RY = 4;
|
||||
|
||||
Display display = Display.getCurrent();
|
||||
if (display == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Image image = new Image(display, SX, SY);
|
||||
|
||||
image.setBackground(display.getSystemColor(SWT.COLOR_WHITE));
|
||||
|
||||
GC gc = new GC(image);
|
||||
gc.setAdvanced(true);
|
||||
gc.setAntialias(SWT.ON);
|
||||
gc.setTextAntialias(SWT.ON);
|
||||
|
||||
gc.setBackground(display.getSystemColor(SWT.COLOR_WHITE));
|
||||
if (mShape == SHAPE_CIRCLE) {
|
||||
gc.fillOval(0, 0, SX - 1, SY - 1);
|
||||
} else if (mShape == SHAPE_RECT) {
|
||||
gc.fillRoundRectangle(0, 0, SX - 1, SY - 1, RX, RY);
|
||||
}
|
||||
|
||||
gc.setForeground(display.getSystemColor(SWT.COLOR_BLACK));
|
||||
gc.setLineWidth(1);
|
||||
if (mShape == SHAPE_CIRCLE) {
|
||||
gc.drawOval(0, 0, SX - 1, SY - 1);
|
||||
} else if (mShape == SHAPE_RECT) {
|
||||
gc.drawRoundRectangle(0, 0, SX - 1, SY - 1, RX, RY);
|
||||
}
|
||||
|
||||
// Get a bold version of the default system font, if possible.
|
||||
Font font = display.getSystemFont();
|
||||
FontData[] fds = font.getFontData();
|
||||
fds[0].setStyle(SWT.BOLD);
|
||||
// use 3/4th of the circle diameter for the font size (in pixels)
|
||||
// and convert it to "font points" (font points in SWT are hardcoded in an
|
||||
// arbitrary 72 dpi and then converted in real pixels using whatever is
|
||||
// indicated by getDPI -- at least that's how it works under Win32).
|
||||
fds[0].setHeight((int) ((SY + 1) * 3./4. * 72./display.getDPI().y));
|
||||
// Note: win32 implementation always uses fds[0] so we change just that one.
|
||||
// getFontData indicates that the array of fd is really an unusual thing for X11.
|
||||
font = new Font(display, fds);
|
||||
gc.setFont(font);
|
||||
gc.setForeground(display.getSystemColor(mColor));
|
||||
|
||||
// Text measurement varies so slightly depending on the platform
|
||||
int ofx = 0;
|
||||
int ofy = 0;
|
||||
if (AndroidConstants.CURRENT_PLATFORM == AndroidConstants.PLATFORM_WINDOWS) {
|
||||
ofx = +1;
|
||||
ofy = -1;
|
||||
}
|
||||
|
||||
String s = Character.toString(mLetter).toUpperCase();
|
||||
Point p = gc.textExtent(s);
|
||||
int tx = (SX + ofx - p.x) / 2;
|
||||
int ty = (SY + ofy - p.y) / 2;
|
||||
gc.drawText(s, tx, ty, true /* isTransparent */);
|
||||
|
||||
font.dispose();
|
||||
gc.dispose();
|
||||
|
||||
ImageData data = image.getImageData();
|
||||
image.dispose();
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.editors.descriptors;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.editors.IconFactory;
|
||||
import com.android.ide.eclipse.editors.uimodel.UiAttributeNode;
|
||||
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
|
||||
|
||||
import org.eclipse.swt.graphics.Image;
|
||||
|
||||
/**
|
||||
* {@link AttributeDescriptor} describes an XML attribute with its XML attribute name.
|
||||
* <p/>
|
||||
* An attribute descriptor also knows which UI node should be instantiated to represent
|
||||
* this particular attribute (e.g. text field, icon chooser, class selector, etc.)
|
||||
* Some attributes may be hidden and have no user interface at all.
|
||||
* <p/>
|
||||
* This is an abstract class. Derived classes must implement data description and return
|
||||
* the correct UiAttributeNode-derived class.
|
||||
*/
|
||||
public abstract class AttributeDescriptor {
|
||||
private String mXmlLocalName;
|
||||
private ElementDescriptor mParent;
|
||||
private final String mNsUri;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AttributeDescriptor}
|
||||
*
|
||||
* @param xmlLocalName The XML name of the attribute (case sensitive)
|
||||
* @param nsUri The URI of the attribute. Can be null if attribute has no namespace.
|
||||
* See {@link AndroidConstants#NS_RESOURCES} for a common value.
|
||||
*/
|
||||
public AttributeDescriptor(String xmlLocalName, String nsUri) {
|
||||
mXmlLocalName = xmlLocalName;
|
||||
mNsUri = nsUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the XML local name of the attribute (case sensitive)
|
||||
*/
|
||||
public final String getXmlLocalName() {
|
||||
return mXmlLocalName;
|
||||
}
|
||||
|
||||
public final String getNamespaceUri() {
|
||||
return mNsUri;
|
||||
}
|
||||
|
||||
final void setParent(ElementDescriptor parent) {
|
||||
mParent = parent;
|
||||
}
|
||||
|
||||
public final ElementDescriptor getParent() {
|
||||
return mParent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an optional icon for the attribute.
|
||||
* <p/>
|
||||
* By default this tries to return an icon based on the XML name of the attribute.
|
||||
* If this fails, it tries to return the default Android logo as defined in the
|
||||
* plugin. If all fails, it returns null.
|
||||
*
|
||||
* @return An icon for this element or null.
|
||||
*/
|
||||
public Image getIcon() {
|
||||
IconFactory factory = IconFactory.getInstance();
|
||||
Image icon;
|
||||
icon = factory.getIcon(getXmlLocalName(), IconFactory.COLOR_RED, IconFactory.SHAPE_CIRCLE);
|
||||
return icon != null ? icon : AdtPlugin.getAndroidLogo();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uiParent The {@link UiElementNode} parent of this UI attribute.
|
||||
* @return A new {@link UiAttributeNode} linked to this descriptor or null if this
|
||||
* attribute has no user interface.
|
||||
*/
|
||||
public abstract UiAttributeNode createUiNode(UiElementNode uiParent);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.editors.descriptors;
|
||||
|
||||
import com.android.ide.eclipse.editors.uimodel.UiAbstractTextAttributeNode;
|
||||
|
||||
import org.eclipse.jface.viewers.ILabelProvider;
|
||||
import org.eclipse.jface.viewers.ILabelProviderListener;
|
||||
import org.eclipse.swt.graphics.Image;
|
||||
|
||||
/**
|
||||
* Label provider for {@link UiAbstractTextAttributeNode}.
|
||||
*/
|
||||
public class AttributeDescriptorLabelProvider implements ILabelProvider {
|
||||
|
||||
private final static AttributeDescriptorLabelProvider sThis =
|
||||
new AttributeDescriptorLabelProvider();
|
||||
|
||||
public static ILabelProvider getProvider() {
|
||||
return sThis;
|
||||
}
|
||||
|
||||
public Image getImage(Object element) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getText(Object element) {
|
||||
if (element instanceof UiAbstractTextAttributeNode) {
|
||||
return ((UiAbstractTextAttributeNode)element).getCurrentValue();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addListener(ILabelProviderListener listener) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
public boolean isLabelProperty(Object element, String property) {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
public void removeListener(ILabelProviderListener listener) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.editors.descriptors;
|
||||
|
||||
import com.android.ide.eclipse.editors.uimodel.UiListAttributeNode;
|
||||
|
||||
/**
|
||||
* Describes a text attribute that can only contain boolean values.
|
||||
* It is displayed by a {@link UiListAttributeNode}.
|
||||
*/
|
||||
public class BooleanAttributeDescriptor extends ListAttributeDescriptor {
|
||||
|
||||
public BooleanAttributeDescriptor(String xmlLocalName, String uiName, String nsUri,
|
||||
String tooltip) {
|
||||
super(xmlLocalName, uiName, nsUri, tooltip,
|
||||
new String[] { "true", "false" } );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,809 @@
|
||||
/*
|
||||
* 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.editors.descriptors;
|
||||
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.common.resources.ResourceType;
|
||||
import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo;
|
||||
import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo.Format;
|
||||
import com.android.ide.eclipse.editors.layout.LayoutConstants;
|
||||
import com.android.ide.eclipse.editors.uimodel.UiDocumentNode;
|
||||
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
|
||||
|
||||
import org.eclipse.swt.graphics.Image;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
/**
|
||||
* Utility methods related to descriptors handling.
|
||||
*/
|
||||
public final class DescriptorsUtils {
|
||||
|
||||
private static final String DEFAULT_WIDGET_PREFIX = "widget";
|
||||
|
||||
private static final int JAVADOC_BREAK_LENGTH = 60;
|
||||
|
||||
/**
|
||||
* The path in the online documentation for the manifest description.
|
||||
* <p/>
|
||||
* This is NOT a complete URL. To be used, it needs to be appended
|
||||
* to {@link AndroidConstants#CODESITE_BASE_URL} or to the local SDK
|
||||
* documentation.
|
||||
*/
|
||||
public static final String MANIFEST_SDK_URL = "/reference/android/R.styleable.html#"; //$NON-NLS-1$
|
||||
|
||||
public static final String IMAGE_KEY = "image"; //$NON-NLS-1$
|
||||
|
||||
private static final String CODE = "$code"; //$NON-NLS-1$
|
||||
private static final String LINK = "$link"; //$NON-NLS-1$
|
||||
private static final String ELEM = "$elem"; //$NON-NLS-1$
|
||||
private static final String BREAK = "$break"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* The {@link ITextAttributeCreator} interface is used by the appendAttribute() method
|
||||
* to provide a way for caller to override the kind of {@link TextAttributeDescriptor}
|
||||
* created for a give XML attribute name.
|
||||
*/
|
||||
public interface ITextAttributeCreator {
|
||||
/**
|
||||
* Creates a new {@link TextAttributeDescriptor} instance for the given XML name,
|
||||
* UI name and tooltip.
|
||||
*
|
||||
* @param xmlName The XML attribute name.
|
||||
* @param uiName The UI attribute name.
|
||||
* @param nsUri The URI of the attribute. Can be null if attribute has no namespace.
|
||||
* See {@link AndroidConstants#NS_RESOURCES} for a common value.
|
||||
* @param tooltip An optional tooltip.
|
||||
* @return A new {@link TextAttributeDescriptor} (or derived) instance.
|
||||
*/
|
||||
public TextAttributeDescriptor create(String xmlName, String uiName, String nsUri,
|
||||
String tooltip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all {@link AttributeInfo} to the the array of {@link AttributeDescriptor}.
|
||||
*
|
||||
* @param attributes The list of {@link AttributeDescriptor} to append to
|
||||
* @param elementXmlName Optional XML local name of the element to which attributes are
|
||||
* being added. When not null, this is used to filter overrides.
|
||||
* @param nsUri The URI of the attribute. Can be null if attribute has no namespace.
|
||||
* See {@link AndroidConstants#NS_RESOURCES} for a common value.
|
||||
* @param infos The array of {@link AttributeInfo} to read and append to attributes
|
||||
* @param requiredAttributes An optional list of attributes to mark as "required" (i.e. append
|
||||
* a "*" to their UI name as a hint for the user.)
|
||||
* @param overrides A map [attribute name => TextAttributeDescriptor creator]. A creator
|
||||
* can either by a Class<? extends TextAttributeDescriptor> or an instance of
|
||||
* {@link ITextAttributeCreator} that instantiates the right TextAttributeDescriptor.
|
||||
*/
|
||||
public static void appendAttributes(ArrayList<AttributeDescriptor> attributes,
|
||||
String elementXmlName,
|
||||
String nsUri, AttributeInfo[] infos,
|
||||
String[] requiredAttributes,
|
||||
Map<String, Object> overrides) {
|
||||
for (AttributeInfo info : infos) {
|
||||
boolean required = false;
|
||||
if (requiredAttributes != null) {
|
||||
for(String attr_name : requiredAttributes) {
|
||||
if (attr_name.equals(info.getName())) {
|
||||
required = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
appendAttribute(attributes, elementXmlName, nsUri, info, required, overrides);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an {@link AttributeInfo} to the the array of {@link AttributeDescriptor}.
|
||||
*
|
||||
* @param attributes The list of {@link AttributeDescriptor} to append to
|
||||
* @param elementXmlName Optional XML local name of the element to which attributes are
|
||||
* being added. When not null, this is used to filter overrides.
|
||||
* @param info The {@link AttributeInfo} to append to attributes
|
||||
* @param nsUri The URI of the attribute. Can be null if attribute has no namespace.
|
||||
* See {@link AndroidConstants#NS_RESOURCES} for a common value.
|
||||
* @param required True if the attribute is to be marked as "required" (i.e. append
|
||||
* a "*" to its UI name as a hint for the user.)
|
||||
* @param overrides A map [attribute name => TextAttributeDescriptor creator]. A creator
|
||||
* can either by a Class<? extends TextAttributeDescriptor> or an instance of
|
||||
* {@link ITextAttributeCreator} that instantiates the right TextAttributeDescriptor.
|
||||
*/
|
||||
public static void appendAttribute(ArrayList<AttributeDescriptor> attributes,
|
||||
String elementXmlName,
|
||||
String nsUri,
|
||||
AttributeInfo info, boolean required,
|
||||
Map<String, Object> overrides) {
|
||||
AttributeDescriptor attr = null;
|
||||
|
||||
String xmlLocalName = info.getName();
|
||||
String uiName = prettyAttributeUiName(info.getName()); // ui_name
|
||||
if (required) {
|
||||
uiName += "*"; //$NON-NLS-1$
|
||||
}
|
||||
String tooltip = formatTooltip(info.getJavaDoc()); // tooltip
|
||||
|
||||
// Add the known types to the tooltip
|
||||
Format[] formats_list = info.getFormats();
|
||||
int flen = formats_list.length;
|
||||
if (flen > 0) {
|
||||
// Fill the formats in a set for faster access
|
||||
HashSet<Format> formats_set = new HashSet<Format>();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (tooltip != null) {
|
||||
sb.append(tooltip);
|
||||
sb.append(" "); //$NON-NLS-1$
|
||||
}
|
||||
sb.append("["); //$NON-NLS-1$
|
||||
for (int i = 0; i < flen; i++) {
|
||||
Format f = formats_list[i];
|
||||
formats_set.add(f);
|
||||
|
||||
sb.append(f.toString().toLowerCase());
|
||||
if (i < flen - 1) {
|
||||
sb.append(", "); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
// The extra space at the end makes the tooltip more readable on Windows.
|
||||
sb.append("]"); //$NON-NLS-1$
|
||||
|
||||
if (required) {
|
||||
sb.append(". Required.");
|
||||
}
|
||||
|
||||
// The extra space at the end makes the tooltip more readable on Windows.
|
||||
sb.append(" "); //$NON-NLS-1$
|
||||
|
||||
tooltip = sb.toString();
|
||||
|
||||
// Create a specialized attribute if we can
|
||||
if (overrides != null) {
|
||||
for (Entry<String, Object> entry: overrides.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String elements[] = key.split("/");
|
||||
String overrideAttrLocalName = null;
|
||||
if (elements.length < 1) {
|
||||
continue;
|
||||
} else if (elements.length == 1) {
|
||||
overrideAttrLocalName = elements[0];
|
||||
elements = null;
|
||||
} else {
|
||||
overrideAttrLocalName = elements[elements.length - 1];
|
||||
elements = elements[0].split(",");
|
||||
}
|
||||
|
||||
if (overrideAttrLocalName == null ||
|
||||
!overrideAttrLocalName.equals(xmlLocalName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean ok_element = elements.length < 1;
|
||||
if (!ok_element) {
|
||||
for (String element : elements) {
|
||||
if (element.equals("*") || element.equals(elementXmlName)) {
|
||||
ok_element = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ok_element) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object override = entry.getValue();
|
||||
if (override instanceof Class) {
|
||||
try {
|
||||
// The override is instance of the class to create, which must
|
||||
// have a constructor compatible with TextAttributeDescriptor.
|
||||
@SuppressWarnings("unchecked") //$NON-NLS-1$
|
||||
Class<? extends TextAttributeDescriptor> clazz =
|
||||
(Class<? extends TextAttributeDescriptor>) override;
|
||||
Constructor<? extends TextAttributeDescriptor> cons;
|
||||
cons = clazz.getConstructor(new Class<?>[] {
|
||||
String.class, String.class, String.class, String.class } );
|
||||
attr = cons.newInstance(
|
||||
new Object[] { xmlLocalName, uiName, nsUri, tooltip });
|
||||
} catch (SecurityException e) {
|
||||
// ignore
|
||||
} catch (NoSuchMethodException e) {
|
||||
// ignore
|
||||
} catch (IllegalArgumentException e) {
|
||||
// ignore
|
||||
} catch (InstantiationException e) {
|
||||
// ignore
|
||||
} catch (IllegalAccessException e) {
|
||||
// ignore
|
||||
} catch (InvocationTargetException e) {
|
||||
// ignore
|
||||
}
|
||||
} else if (override instanceof ITextAttributeCreator) {
|
||||
attr = ((ITextAttributeCreator) override).create(
|
||||
xmlLocalName, uiName, nsUri, tooltip);
|
||||
}
|
||||
}
|
||||
} // if overrides
|
||||
|
||||
// Create a specialized descriptor if we can, based on type
|
||||
if (attr == null) {
|
||||
if (formats_set.contains(Format.REFERENCE)) {
|
||||
// This is either a multi-type reference or a generic reference.
|
||||
attr = new ReferenceAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip);
|
||||
} else if (formats_set.contains(Format.ENUM)) {
|
||||
attr = new ListAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip,
|
||||
info.getEnumValues());
|
||||
} else if (formats_set.contains(Format.FLAG)) {
|
||||
attr = new FlagAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip,
|
||||
info.getFlagValues());
|
||||
} else if (formats_set.contains(Format.BOOLEAN)) {
|
||||
attr = new BooleanAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip);
|
||||
} else if (formats_set.contains(Format.STRING)) {
|
||||
attr = new ReferenceAttributeDescriptor(ResourceType.STRING,
|
||||
xmlLocalName, uiName, nsUri,
|
||||
tooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// By default a simple text field is used
|
||||
if (attr == null) {
|
||||
attr = new TextAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip);
|
||||
}
|
||||
attributes.add(attr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates the the given {@link AttributeInfo} already exists in the ArrayList of
|
||||
* {@link AttributeDescriptor}. This test for the presence of a descriptor with the same
|
||||
* XML name.
|
||||
*
|
||||
* @param attributes The list of {@link AttributeDescriptor} to compare to.
|
||||
* @param nsUri The URI of the attribute. Can be null if attribute has no namespace.
|
||||
* See {@link AndroidConstants#NS_RESOURCES} for a common value.
|
||||
* @param info The {@link AttributeInfo} to know whether it is included in the above list.
|
||||
* @return True if this {@link AttributeInfo} is already present in
|
||||
* the {@link AttributeDescriptor} list.
|
||||
*/
|
||||
public static boolean containsAttribute(ArrayList<AttributeDescriptor> attributes,
|
||||
String nsUri,
|
||||
AttributeInfo info) {
|
||||
String xmlLocalName = info.getName();
|
||||
for (AttributeDescriptor desc : attributes) {
|
||||
if (desc.getXmlLocalName().equals(xmlLocalName)) {
|
||||
if (nsUri == desc.getNamespaceUri() ||
|
||||
(nsUri != null && nsUri.equals(desc.getNamespaceUri()))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a pretty attribute UI name from an XML name.
|
||||
* <p/>
|
||||
* The original xml name starts with a lower case and is camel-case,
|
||||
* e.g. "maxWidthForView". The pretty name starts with an upper case
|
||||
* and has space separators, e.g. "Max width for view".
|
||||
*/
|
||||
public static String prettyAttributeUiName(String name) {
|
||||
if (name.length() < 1) {
|
||||
return name;
|
||||
}
|
||||
StringBuffer buf = new StringBuffer();
|
||||
|
||||
char c = name.charAt(0);
|
||||
// Use upper case initial letter
|
||||
buf.append((char)(c >= 'a' && c <= 'z' ? c + 'A' - 'a' : c));
|
||||
int len = name.length();
|
||||
for (int i = 1; i < len; i++) {
|
||||
c = name.charAt(i);
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
// Break camel case into separate words
|
||||
buf.append(' ');
|
||||
// Use a lower case initial letter for the next word, except if the
|
||||
// word is solely X, Y or Z.
|
||||
if (c >= 'X' && c <= 'Z' &&
|
||||
(i == len-1 ||
|
||||
(i < len-1 && name.charAt(i+1) >= 'A' && name.charAt(i+1) <= 'Z'))) {
|
||||
buf.append(c);
|
||||
} else {
|
||||
buf.append((char)(c - 'A' + 'a'));
|
||||
}
|
||||
} else if (c == '_') {
|
||||
buf.append(' ');
|
||||
} else {
|
||||
buf.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
name = buf.toString();
|
||||
|
||||
// Replace these acronyms by upper-case versions
|
||||
// - (?<=^| ) means "if preceded by a space or beginning of string"
|
||||
// - (?=$| ) means "if followed by a space or end of string"
|
||||
name = name.replaceAll("(?<=^| )sdk(?=$| )", "SDK");
|
||||
name = name.replaceAll("(?<=^| )uri(?=$| )", "URI");
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Capitalizes the string, i.e. transforms the initial [a-z] into [A-Z].
|
||||
* Returns the string unmodified if the first character is not [a-z].
|
||||
*
|
||||
* @param str The string to capitalize.
|
||||
* @return The capitalized string
|
||||
*/
|
||||
public static String capitalize(String str) {
|
||||
if (str == null || str.length() < 1 || str.charAt(0) < 'a' || str.charAt(0) > 'z') {
|
||||
return str;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append((char)(str.charAt(0) + 'A' - 'a'));
|
||||
sb.append(str.substring(1));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the javadoc tooltip to be usable in a tooltip.
|
||||
*/
|
||||
public static String formatTooltip(String javadoc) {
|
||||
ArrayList<String> spans = scanJavadoc(javadoc);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean needBreak = false;
|
||||
|
||||
for (int n = spans.size(), i = 0; i < n; ++i) {
|
||||
String s = spans.get(i);
|
||||
if (CODE.equals(s)) {
|
||||
s = spans.get(++i);
|
||||
if (s != null) {
|
||||
sb.append('"').append(s).append('"');
|
||||
}
|
||||
} else if (LINK.equals(s)) {
|
||||
String base = spans.get(++i);
|
||||
String anchor = spans.get(++i);
|
||||
String text = spans.get(++i);
|
||||
|
||||
if (base != null) {
|
||||
base = base.trim();
|
||||
}
|
||||
if (anchor != null) {
|
||||
anchor = anchor.trim();
|
||||
}
|
||||
if (text != null) {
|
||||
text = text.trim();
|
||||
}
|
||||
|
||||
// If there's no text, use the anchor if there's one
|
||||
if (text == null || text.length() == 0) {
|
||||
text = anchor;
|
||||
}
|
||||
|
||||
if (base != null && base.length() > 0) {
|
||||
if (text == null || text.length() == 0) {
|
||||
// If we still have no text, use the base as text
|
||||
text = base;
|
||||
}
|
||||
}
|
||||
|
||||
if (text != null) {
|
||||
sb.append(text);
|
||||
}
|
||||
|
||||
} else if (ELEM.equals(s)) {
|
||||
s = spans.get(++i);
|
||||
if (s != null) {
|
||||
sb.append(s);
|
||||
}
|
||||
} else if (BREAK.equals(s)) {
|
||||
needBreak = true;
|
||||
} else if (s != null) {
|
||||
if (needBreak && s.trim().length() > 0) {
|
||||
sb.append('\r');
|
||||
}
|
||||
sb.append(s);
|
||||
needBreak = false;
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the javadoc tooltip to be usable in a FormText.
|
||||
* <p/>
|
||||
* If the descriptor can provide an icon, the caller should provide
|
||||
* elementsDescriptor.getIcon() as "image" to FormText, e.g.:
|
||||
* <code>formText.setImage(IMAGE_KEY, elementsDescriptor.getIcon());</code>
|
||||
*
|
||||
* @param javadoc The javadoc to format. Cannot be null.
|
||||
* @param elementDescriptor The element descriptor parent of the javadoc. Cannot be null.
|
||||
* @param androidDocBaseUrl The base URL for the documentation. Cannot be null. Should be
|
||||
* <code>FrameworkResourceManager.getInstance().getDocumentationBaseUrl()</code>
|
||||
*/
|
||||
public static String formatFormText(String javadoc,
|
||||
ElementDescriptor elementDescriptor,
|
||||
String androidDocBaseUrl) {
|
||||
ArrayList<String> spans = scanJavadoc(javadoc);
|
||||
|
||||
String fullSdkUrl = androidDocBaseUrl + MANIFEST_SDK_URL;
|
||||
String sdkUrl = elementDescriptor.getSdkUrl();
|
||||
if (sdkUrl != null && sdkUrl.startsWith(MANIFEST_SDK_URL)) {
|
||||
fullSdkUrl = androidDocBaseUrl + sdkUrl;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
Image icon = elementDescriptor.getIcon();
|
||||
if (icon != null) {
|
||||
sb.append("<form><li style=\"image\" value=\"" + //$NON-NLS-1$
|
||||
IMAGE_KEY + "\">"); //$NON-NLS-1$
|
||||
} else {
|
||||
sb.append("<form><p>"); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
for (int n = spans.size(), i = 0; i < n; ++i) {
|
||||
String s = spans.get(i);
|
||||
if (CODE.equals(s)) {
|
||||
s = spans.get(++i);
|
||||
if (elementDescriptor.getXmlName().equals(s) && fullSdkUrl != null) {
|
||||
sb.append("<a href=\""); //$NON-NLS-1$
|
||||
sb.append(fullSdkUrl);
|
||||
sb.append("\">"); //$NON-NLS-1$
|
||||
sb.append(s);
|
||||
sb.append("</a>"); //$NON-NLS-1$
|
||||
} else if (s != null) {
|
||||
sb.append('"').append(s).append('"');
|
||||
}
|
||||
} else if (LINK.equals(s)) {
|
||||
String base = spans.get(++i);
|
||||
String anchor = spans.get(++i);
|
||||
String text = spans.get(++i);
|
||||
|
||||
if (base != null) {
|
||||
base = base.trim();
|
||||
}
|
||||
if (anchor != null) {
|
||||
anchor = anchor.trim();
|
||||
}
|
||||
if (text != null) {
|
||||
text = text.trim();
|
||||
}
|
||||
|
||||
// If there's no text, use the anchor if there's one
|
||||
if (text == null || text.length() == 0) {
|
||||
text = anchor;
|
||||
}
|
||||
|
||||
// TODO specialize with a base URL for views, menus & other resources
|
||||
// Base is empty for a local page anchor, in which case we'll replace it
|
||||
// by the element SDK URL if it exists.
|
||||
if ((base == null || base.length() == 0) && fullSdkUrl != null) {
|
||||
base = fullSdkUrl;
|
||||
}
|
||||
|
||||
String url = null;
|
||||
if (base != null && base.length() > 0) {
|
||||
if (base.startsWith("http")) { //$NON-NLS-1$
|
||||
// If base looks an URL, use it, with the optional anchor
|
||||
url = base;
|
||||
if (anchor != null && anchor.length() > 0) {
|
||||
// If the base URL already has an anchor, it needs to be
|
||||
// removed first. If there's no anchor, we need to add "#"
|
||||
int pos = url.lastIndexOf('#');
|
||||
if (pos < 0) {
|
||||
url += "#"; //$NON-NLS-1$
|
||||
} else if (pos < url.length() - 1) {
|
||||
url = url.substring(0, pos + 1);
|
||||
}
|
||||
|
||||
url += anchor;
|
||||
}
|
||||
} else if (text == null || text.length() == 0) {
|
||||
// If we still have no text, use the base as text
|
||||
text = base;
|
||||
}
|
||||
}
|
||||
|
||||
if (url != null && text != null) {
|
||||
sb.append("<a href=\""); //$NON-NLS-1$
|
||||
sb.append(url);
|
||||
sb.append("\">"); //$NON-NLS-1$
|
||||
sb.append(text);
|
||||
sb.append("</a>"); //$NON-NLS-1$
|
||||
} else if (text != null) {
|
||||
sb.append("<b>").append(text).append("</b>"); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
}
|
||||
|
||||
} else if (ELEM.equals(s)) {
|
||||
s = spans.get(++i);
|
||||
if (sdkUrl != null && s != null) {
|
||||
sb.append("<a href=\""); //$NON-NLS-1$
|
||||
sb.append(sdkUrl);
|
||||
sb.append("\">"); //$NON-NLS-1$
|
||||
sb.append(s);
|
||||
sb.append("</a>"); //$NON-NLS-1$
|
||||
} else if (s != null) {
|
||||
sb.append("<b>").append(s).append("</b>"); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
}
|
||||
} else if (BREAK.equals(s)) {
|
||||
// ignore line breaks in pseudo-HTML rendering
|
||||
} else if (s != null) {
|
||||
sb.append(s);
|
||||
}
|
||||
}
|
||||
|
||||
if (icon != null) {
|
||||
sb.append("</li></form>"); //$NON-NLS-1$
|
||||
} else {
|
||||
sb.append("</p></form>"); //$NON-NLS-1$
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static ArrayList<String> scanJavadoc(String javadoc) {
|
||||
ArrayList<String> spans = new ArrayList<String>();
|
||||
|
||||
// Standardize all whitespace in the javadoc to single spaces.
|
||||
if (javadoc != null) {
|
||||
javadoc = javadoc.replaceAll("[ \t\f\r\n]+", " "); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
}
|
||||
|
||||
// Detects {@link <base>#<name> <text>} where all 3 are optional
|
||||
Pattern p_link = Pattern.compile("\\{@link\\s+([^#\\}\\s]*)(?:#([^\\s\\}]*))?(?:\\s*([^\\}]*))?\\}(.*)"); //$NON-NLS-1$
|
||||
// Detects <code>blah</code>
|
||||
Pattern p_code = Pattern.compile("<code>(.+?)</code>(.*)"); //$NON-NLS-1$
|
||||
// Detects @blah@, used in hard-coded tooltip descriptors
|
||||
Pattern p_elem = Pattern.compile("@([\\w -]+)@(.*)"); //$NON-NLS-1$
|
||||
// Detects a buffer that starts by @ < or { (one that was not matched above)
|
||||
Pattern p_open = Pattern.compile("([@<\\{])(.*)"); //$NON-NLS-1$
|
||||
// Detects everything till the next potential separator, i.e. @ < or {
|
||||
Pattern p_text = Pattern.compile("([^@<\\{]+)(.*)"); //$NON-NLS-1$
|
||||
|
||||
int currentLength = 0;
|
||||
String text = null;
|
||||
|
||||
while(javadoc != null && javadoc.length() > 0) {
|
||||
Matcher m;
|
||||
String s = null;
|
||||
if ((m = p_code.matcher(javadoc)).matches()) {
|
||||
spans.add(CODE);
|
||||
spans.add(text = cleanupJavadocHtml(m.group(1))); // <code> text
|
||||
javadoc = m.group(2);
|
||||
if (text != null) {
|
||||
currentLength += text.length();
|
||||
}
|
||||
} else if ((m = p_link.matcher(javadoc)).matches()) {
|
||||
spans.add(LINK);
|
||||
spans.add(m.group(1)); // @link base
|
||||
spans.add(m.group(2)); // @link anchor
|
||||
spans.add(text = cleanupJavadocHtml(m.group(3))); // @link text
|
||||
javadoc = m.group(4);
|
||||
if (text != null) {
|
||||
currentLength += text.length();
|
||||
}
|
||||
} else if ((m = p_elem.matcher(javadoc)).matches()) {
|
||||
spans.add(ELEM);
|
||||
spans.add(text = cleanupJavadocHtml(m.group(1))); // @text@
|
||||
javadoc = m.group(2);
|
||||
if (text != null) {
|
||||
currentLength += text.length() - 2;
|
||||
}
|
||||
} else if ((m = p_open.matcher(javadoc)).matches()) {
|
||||
s = m.group(1);
|
||||
javadoc = m.group(2);
|
||||
} else if ((m = p_text.matcher(javadoc)).matches()) {
|
||||
s = m.group(1);
|
||||
javadoc = m.group(2);
|
||||
} else {
|
||||
// This is not supposed to happen. In case of, just use everything.
|
||||
s = javadoc;
|
||||
javadoc = null;
|
||||
}
|
||||
if (s != null && s.length() > 0) {
|
||||
s = cleanupJavadocHtml(s);
|
||||
|
||||
if (currentLength >= JAVADOC_BREAK_LENGTH) {
|
||||
spans.add(BREAK);
|
||||
currentLength = 0;
|
||||
}
|
||||
while (currentLength + s.length() > JAVADOC_BREAK_LENGTH) {
|
||||
int pos = s.indexOf(' ', JAVADOC_BREAK_LENGTH - currentLength);
|
||||
if (pos <= 0) {
|
||||
break;
|
||||
}
|
||||
spans.add(s.substring(0, pos + 1));
|
||||
spans.add(BREAK);
|
||||
currentLength = 0;
|
||||
s = s.substring(pos + 1);
|
||||
}
|
||||
|
||||
spans.add(s);
|
||||
currentLength += s.length();
|
||||
}
|
||||
}
|
||||
|
||||
return spans;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove anything that looks like HTML from a javadoc snippet, as it is supported
|
||||
* neither by FormText nor a standard text tooltip.
|
||||
*/
|
||||
private static String cleanupJavadocHtml(String s) {
|
||||
if (s != null) {
|
||||
s = s.replaceAll("<", "\""); //$NON-NLS-1$ $NON-NLS-2$
|
||||
s = s.replaceAll(">", "\""); //$NON-NLS-1$ $NON-NLS-2$
|
||||
s = s.replaceAll("<[^>]+>", ""); //$NON-NLS-1$ $NON-NLS-2$
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default layout attributes for the a new UiElementNode.
|
||||
* <p/>
|
||||
* Note that ideally the node should already be part of a hierarchy so that its
|
||||
* parent layout and previous sibling can be determined, if any.
|
||||
* <p/>
|
||||
* This does not override attributes which are not empty.
|
||||
*/
|
||||
public static void setDefaultLayoutAttributes(UiElementNode ui_node, boolean updateLayout) {
|
||||
// if this ui_node is a layout and we're adding it to a document, use fill_parent for
|
||||
// both W/H. Otherwise default to wrap_layout.
|
||||
boolean fill = ui_node.getDescriptor().hasChildren() &&
|
||||
ui_node.getUiParent() instanceof UiDocumentNode;
|
||||
ui_node.setAttributeValue(LayoutConstants.ATTR_LAYOUT_WIDTH,
|
||||
fill ? LayoutConstants.VALUE_FILL_PARENT : LayoutConstants.VALUE_WRAP_CONTENT,
|
||||
false /* override */);
|
||||
ui_node.setAttributeValue(LayoutConstants.ATTR_LAYOUT_HEIGHT,
|
||||
fill ? LayoutConstants.VALUE_FILL_PARENT : LayoutConstants.VALUE_WRAP_CONTENT,
|
||||
false /* override */);
|
||||
|
||||
String widget_id = getFreeWidgetId(ui_node.getUiRoot(),
|
||||
new Object[] { ui_node.getDescriptor().getXmlLocalName(), null, null, null });
|
||||
if (widget_id != null) {
|
||||
ui_node.setAttributeValue(LayoutConstants.ATTR_ID, "@+id/" + widget_id, //$NON-NLS-1$
|
||||
false /* override */);
|
||||
}
|
||||
|
||||
ui_node.setAttributeValue(LayoutConstants.ATTR_TEXT, widget_id, false /*override*/);
|
||||
|
||||
if (updateLayout) {
|
||||
UiElementNode ui_parent = ui_node.getUiParent();
|
||||
if (ui_parent != null &&
|
||||
ui_parent.getDescriptor().getXmlLocalName().equals(
|
||||
LayoutConstants.RELATIVE_LAYOUT)) {
|
||||
UiElementNode ui_previous = ui_node.getUiPreviousSibling();
|
||||
if (ui_previous != null) {
|
||||
String id = ui_previous.getAttributeValue(LayoutConstants.ATTR_ID);
|
||||
if (id != null && id.length() > 0) {
|
||||
id = id.replace("@+", "@"); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
ui_node.setAttributeValue(LayoutConstants.ATTR_LAYOUT_BELOW, id,
|
||||
false /* override */);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a UI root node, returns the first available id that matches the
|
||||
* pattern "prefix%02d".
|
||||
*
|
||||
* @param uiNode The UI node that gives the prefix to match.
|
||||
* @return A suitable generated id
|
||||
*/
|
||||
public static String getFreeWidgetId(UiElementNode uiNode) {
|
||||
return getFreeWidgetId(uiNode.getUiRoot(),
|
||||
new Object[] { uiNode.getDescriptor().getXmlLocalName(), null, null, null });
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a UI root node, returns the first available id that matches the
|
||||
* pattern "prefix%02d".
|
||||
*
|
||||
* For recursion purposes, a "context" is given. Since Java doesn't have in-out parameters
|
||||
* in methods and we're not going to do a dedicated type, we just use an object array which
|
||||
* must contain one initial item and several are built on the fly just for internal storage:
|
||||
* <ul>
|
||||
* <li> prefix(String): The prefix of the generated id, i.e. "widget". Cannot be null.
|
||||
* <li> index(Integer): The minimum index of the generated id. Must start with null.
|
||||
* <li> generated(String): The generated widget currently being searched. Must start with null.
|
||||
* <li> map(Set<String>): A set of the ids collected so far when walking through the widget
|
||||
* hierarchy. Must start with null.
|
||||
* </ul>
|
||||
*
|
||||
* @param uiRoot The Ui root node where to start searching recursively. For the initial call
|
||||
* you want to pass the document root.
|
||||
* @param params An in-out context of parameters used during recursion, as explained above.
|
||||
* @return A suitable generated id
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static String getFreeWidgetId(UiElementNode uiRoot,
|
||||
Object[] params) {
|
||||
|
||||
Set<String> map = (Set<String>)params[3];
|
||||
if (map == null) {
|
||||
params[3] = map = new HashSet<String>();
|
||||
}
|
||||
|
||||
int num = params[1] == null ? 0 : ((Integer)params[1]).intValue();
|
||||
|
||||
String generated = (String) params[2];
|
||||
String prefix = (String) params[0];
|
||||
if (generated == null) {
|
||||
int pos = prefix.indexOf('.');
|
||||
if (pos >= 0) {
|
||||
prefix = prefix.substring(pos + 1);
|
||||
}
|
||||
pos = prefix.indexOf('$');
|
||||
if (pos >= 0) {
|
||||
prefix = prefix.substring(pos + 1);
|
||||
}
|
||||
prefix = prefix.replaceAll("[^a-zA-Z]", ""); //$NON-NLS-1$ $NON-NLS-2$
|
||||
if (prefix.length() == 0) {
|
||||
prefix = DEFAULT_WIDGET_PREFIX;
|
||||
}
|
||||
|
||||
do {
|
||||
num++;
|
||||
generated = String.format("%1$s%2$02d", prefix, num); //$NON-NLS-1$
|
||||
} while (map.contains(generated));
|
||||
|
||||
params[0] = prefix;
|
||||
params[1] = num;
|
||||
params[2] = generated;
|
||||
}
|
||||
|
||||
String id = uiRoot.getAttributeValue(LayoutConstants.ATTR_ID);
|
||||
if (id != null) {
|
||||
id = id.replace("@+id/", ""); //$NON-NLS-1$ $NON-NLS-2$
|
||||
id = id.replace("@id/", ""); //$NON-NLS-1$ $NON-NLS-2$
|
||||
if (map.add(id) && map.contains(generated)) {
|
||||
|
||||
do {
|
||||
num++;
|
||||
generated = String.format("%1$s%2$02d", prefix, num); //$NON-NLS-1$
|
||||
} while (map.contains(generated));
|
||||
|
||||
params[1] = num;
|
||||
params[2] = generated;
|
||||
}
|
||||
}
|
||||
|
||||
for (UiElementNode uiChild : uiRoot.getUiChildren()) {
|
||||
getFreeWidgetId(uiChild, params);
|
||||
}
|
||||
|
||||
// Note: return params[2] (not "generated") since it could have changed during recursion.
|
||||
return (String) params[2];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.editors.descriptors;
|
||||
|
||||
import com.android.ide.eclipse.editors.uimodel.UiDocumentNode;
|
||||
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
|
||||
|
||||
/**
|
||||
* {@link DocumentDescriptor} describes the properties expected for an XML document node.
|
||||
*
|
||||
* Compared to ElementDescriptor, {@link DocumentDescriptor} does not have XML name nor UI name,
|
||||
* tooltip, SDK url and attributes list.
|
||||
* <p/>
|
||||
* It has a children list which represent all the possible roots of the document.
|
||||
* <p/>
|
||||
* The document nodes are "mandatory", meaning the UI node is never deleted and it may lack
|
||||
* an actual XML node attached.
|
||||
*/
|
||||
public class DocumentDescriptor extends ElementDescriptor {
|
||||
|
||||
/**
|
||||
* Constructs a new {@link DocumentDescriptor} based on its XML name and children list.
|
||||
* The UI name is build by capitalizing the XML name.
|
||||
* The UI nodes will be non-mandatory.
|
||||
* <p/>
|
||||
* The XML name is never shown in the UI directly. It is however used when an icon
|
||||
* needs to be found for the node.
|
||||
*
|
||||
* @param xml_name The XML element node name. Case sensitive.
|
||||
* @param children The list of allowed children. Can be null or empty.
|
||||
*/
|
||||
public DocumentDescriptor(String xml_name, ElementDescriptor[] children) {
|
||||
super(xml_name, children, true /* mandatory */);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A new {@link UiElementNode} linked to this descriptor.
|
||||
*/
|
||||
@Override
|
||||
public UiElementNode createUiNode() {
|
||||
return new UiDocumentNode(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
/*
|
||||
* 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.editors.descriptors;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.editors.IconFactory;
|
||||
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
|
||||
|
||||
import org.eclipse.jface.resource.ImageDescriptor;
|
||||
import org.eclipse.swt.graphics.Image;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* {@link ElementDescriptor} describes the properties expected for a given XML element node.
|
||||
*
|
||||
* {@link ElementDescriptor} have an XML name, UI name, a tooltip, an SDK url,
|
||||
* an attributes list and a children list.
|
||||
*
|
||||
* An UI node can be "mandatory", meaning the UI node is never deleted and it may lack
|
||||
* an actual XML node attached. A non-mandatory UI node MUST have an XML node attached
|
||||
* and it will cease to exist when the XML node ceases to exist.
|
||||
*/
|
||||
public class ElementDescriptor {
|
||||
/** The XML element node name. Case sensitive. */
|
||||
private String mXmlName;
|
||||
/** The XML element name for the user interface, typically capitalized. */
|
||||
private String mUiName;
|
||||
/** The list of allowed attributes. */
|
||||
private AttributeDescriptor[] mAttributes;
|
||||
/** The list of allowed children */
|
||||
private ElementDescriptor[] mChildren;
|
||||
/* An optional tooltip. Can be empty. */
|
||||
private String mTooltip;
|
||||
/** An optional SKD URL. Can be empty. */
|
||||
private String mSdkUrl;
|
||||
/** Whether this UI node must always exist (even for empty models). */
|
||||
private boolean mMandatory;
|
||||
|
||||
/**
|
||||
* Constructs a new {@link ElementDescriptor} based on its XML name, UI name,
|
||||
* tooltip, SDK url, attributes list, children list and mandatory.
|
||||
*
|
||||
* @param xml_name The XML element node name. Case sensitive.
|
||||
* @param ui_name The XML element name for the user interface, typically capitalized.
|
||||
* @param tooltip An optional tooltip. Can be null or empty.
|
||||
* @param sdk_url An optional SKD URL. Can be null or empty.
|
||||
* @param attributes The list of allowed attributes. Can be null or empty.
|
||||
* @param children The list of allowed children. Can be null or empty.
|
||||
* @param mandatory Whether this node must always exist (even for empty models). A mandatory
|
||||
* UI node is never deleted and it may lack an actual XML node attached. A non-mandatory
|
||||
* UI node MUST have an XML node attached and it will cease to exist when the XML node
|
||||
* ceases to exist.
|
||||
*/
|
||||
public ElementDescriptor(String xml_name, String ui_name, String tooltip, String sdk_url,
|
||||
AttributeDescriptor[] attributes,
|
||||
ElementDescriptor[] children,
|
||||
boolean mandatory) {
|
||||
mMandatory = mandatory;
|
||||
mXmlName = xml_name;
|
||||
mUiName = ui_name;
|
||||
mTooltip = (tooltip != null && tooltip.length() > 0) ? tooltip : null;
|
||||
mSdkUrl = (sdk_url != null && sdk_url.length() > 0) ? sdk_url : null;
|
||||
setAttributes(attributes != null ? attributes : new AttributeDescriptor[]{});
|
||||
mChildren = children != null ? children : new ElementDescriptor[]{};
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link ElementDescriptor} based on its XML name and children list.
|
||||
* The UI name is build by capitalizing the XML name.
|
||||
* The UI nodes will be non-mandatory.
|
||||
*
|
||||
* @param xml_name The XML element node name. Case sensitive.
|
||||
* @param children The list of allowed children. Can be null or empty.
|
||||
* @param mandatory Whether this node must always exist (even for empty models). A mandatory
|
||||
* UI node is never deleted and it may lack an actual XML node attached. A non-mandatory
|
||||
* UI node MUST have an XML node attached and it will cease to exist when the XML node
|
||||
* ceases to exist.
|
||||
*/
|
||||
public ElementDescriptor(String xml_name, ElementDescriptor[] children, boolean mandatory) {
|
||||
this(xml_name, prettyName(xml_name), null, null, null, children, mandatory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link ElementDescriptor} based on its XML name and children list.
|
||||
* The UI name is build by capitalizing the XML name.
|
||||
* The UI nodes will be non-mandatory.
|
||||
*
|
||||
* @param xml_name The XML element node name. Case sensitive.
|
||||
* @param children The list of allowed children. Can be null or empty.
|
||||
*/
|
||||
public ElementDescriptor(String xml_name, ElementDescriptor[] children) {
|
||||
this(xml_name, prettyName(xml_name), null, null, null, children, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link ElementDescriptor} based on its XML name.
|
||||
* The UI name is build by capitalizing the XML name.
|
||||
* The UI nodes will be non-mandatory.
|
||||
*
|
||||
* @param xml_name The XML element node name. Case sensitive.
|
||||
*/
|
||||
public ElementDescriptor(String xml_name) {
|
||||
this(xml_name, prettyName(xml_name), null, null, null, null, false);
|
||||
}
|
||||
|
||||
/** Returns whether this node must always exist (even for empty models) */
|
||||
public boolean isMandatory() {
|
||||
return mMandatory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the XML element node local name (case sensitive)
|
||||
*/
|
||||
public final String getXmlLocalName() {
|
||||
int pos = mXmlName.indexOf(':');
|
||||
if (pos != -1) {
|
||||
return mXmlName.substring(pos+1);
|
||||
}
|
||||
return mXmlName;
|
||||
}
|
||||
|
||||
/** Returns the XML element node name. Case sensitive. */
|
||||
public String getXmlName() {
|
||||
return mXmlName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the namespace of the attribute.
|
||||
*/
|
||||
public final String getNamespace() {
|
||||
// For now we hard-code the prefix as being "android"
|
||||
if (mXmlName.startsWith("android:")) { //$NON-NLs-1$
|
||||
return AndroidConstants.NS_RESOURCES;
|
||||
}
|
||||
|
||||
return ""; //$NON-NLs-1$
|
||||
}
|
||||
|
||||
|
||||
/** Returns the XML element name for the user interface, typically capitalized. */
|
||||
public String getUiName() {
|
||||
return mUiName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an optional icon for the element.
|
||||
* <p/>
|
||||
* By default this tries to return an icon based on the XML name of the element.
|
||||
* If this fails, it tries to return the default Android logo as defined in the
|
||||
* plugin. If all fails, it returns null.
|
||||
*
|
||||
* @return An icon for this element or null.
|
||||
*/
|
||||
public Image getIcon() {
|
||||
IconFactory factory = IconFactory.getInstance();
|
||||
int color = hasChildren() ? IconFactory.COLOR_BLUE : IconFactory.COLOR_GREEN;
|
||||
int shape = hasChildren() ? IconFactory.SHAPE_RECT : IconFactory.SHAPE_CIRCLE;
|
||||
Image icon = factory.getIcon(mXmlName, color, shape);
|
||||
return icon != null ? icon : AdtPlugin.getAndroidLogo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an optional ImageDescriptor for the element.
|
||||
* <p/>
|
||||
* By default this tries to return an image based on the XML name of the element.
|
||||
* If this fails, it tries to return the default Android logo as defined in the
|
||||
* plugin. If all fails, it returns null.
|
||||
*
|
||||
* @return An ImageDescriptor for this element or null.
|
||||
*/
|
||||
public ImageDescriptor getImageDescriptor() {
|
||||
IconFactory factory = IconFactory.getInstance();
|
||||
int color = hasChildren() ? IconFactory.COLOR_BLUE : IconFactory.COLOR_GREEN;
|
||||
int shape = hasChildren() ? IconFactory.SHAPE_RECT : IconFactory.SHAPE_CIRCLE;
|
||||
ImageDescriptor id = factory.getImageDescriptor(mXmlName, color, shape);
|
||||
return id != null ? id : AdtPlugin.getAndroidLogoDesc();
|
||||
}
|
||||
|
||||
/* Returns the list of allowed attributes. */
|
||||
public AttributeDescriptor[] getAttributes() {
|
||||
return mAttributes;
|
||||
}
|
||||
|
||||
/* Sets the list of allowed attributes. */
|
||||
public void setAttributes(AttributeDescriptor[] attributes) {
|
||||
mAttributes = attributes;
|
||||
for (AttributeDescriptor attribute : attributes) {
|
||||
attribute.setParent(this);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the list of allowed children */
|
||||
public ElementDescriptor[] getChildren() {
|
||||
return mChildren;
|
||||
}
|
||||
|
||||
/** @return True if this descriptor has children available */
|
||||
public boolean hasChildren() {
|
||||
return mChildren.length > 0;
|
||||
}
|
||||
|
||||
/** Sets the list of allowed children. */
|
||||
public void setChildren(ElementDescriptor[] newChildren) {
|
||||
mChildren = newChildren;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an optional tooltip. Will be null if not present.
|
||||
* <p/>
|
||||
* The tooltip is based on the Javadoc of the element and already processed via
|
||||
* {@link DescriptorsUtils#formatTooltip(String)} to be displayed right away as
|
||||
* a UI tooltip.
|
||||
*/
|
||||
public String getTooltip() {
|
||||
return mTooltip;
|
||||
}
|
||||
|
||||
/** Returns an optional SKD URL. Will be null if not present. */
|
||||
public String getSdkUrl() {
|
||||
return mSdkUrl;
|
||||
}
|
||||
|
||||
/** Sets the optional tooltip. Can be null or empty. */
|
||||
public void setTooltip(String tooltip) {
|
||||
mTooltip = tooltip;
|
||||
}
|
||||
|
||||
/** Sets the optional SDK URL. Can be null or empty. */
|
||||
public void setSdkUrl(String sdkUrl) {
|
||||
mSdkUrl = sdkUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A new {@link UiElementNode} linked to this descriptor.
|
||||
*/
|
||||
public UiElementNode createUiNode() {
|
||||
return new UiElementNode(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first children of this descriptor that describes the given XML element name.
|
||||
* <p/>
|
||||
* In recursive mode, searches the direct children first before descending in the hierarchy.
|
||||
*
|
||||
* @return The ElementDescriptor matching the requested XML node element name or null.
|
||||
*/
|
||||
public ElementDescriptor findChildrenDescriptor(String element_name, boolean recursive) {
|
||||
return findChildrenDescriptorInternal(element_name, recursive, null);
|
||||
}
|
||||
|
||||
private ElementDescriptor findChildrenDescriptorInternal(String element_name,
|
||||
boolean recursive,
|
||||
Set<ElementDescriptor> visited) {
|
||||
if (recursive && visited == null) {
|
||||
visited = new HashSet<ElementDescriptor>();
|
||||
}
|
||||
|
||||
for (ElementDescriptor e : getChildren()) {
|
||||
if (e.getXmlName().equals(element_name)) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
if (visited != null) {
|
||||
visited.add(this);
|
||||
}
|
||||
|
||||
if (recursive) {
|
||||
for (ElementDescriptor e : getChildren()) {
|
||||
if (visited != null) {
|
||||
if (!visited.add(e)) { // Set.add() returns false if element is already present
|
||||
continue;
|
||||
}
|
||||
}
|
||||
ElementDescriptor f = e.findChildrenDescriptorInternal(element_name,
|
||||
recursive, visited);
|
||||
if (f != null) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility helper than pretty-formats an XML Name for the UI.
|
||||
* This is used by the simplified constructor that takes only an XML element name.
|
||||
*
|
||||
* @param xml_name The XML name to convert.
|
||||
* @return The XML name with dashes replaced by spaces and capitalized.
|
||||
*/
|
||||
private static String prettyName(String xml_name) {
|
||||
char c[] = xml_name.toCharArray();
|
||||
if (c.length > 0) {
|
||||
c[0] = Character.toUpperCase(c[0]);
|
||||
}
|
||||
return new String(c).replace("-", " "); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.editors.descriptors;
|
||||
|
||||
import com.android.ide.eclipse.editors.uimodel.UiAttributeNode;
|
||||
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
|
||||
import com.android.ide.eclipse.editors.uimodel.UiListAttributeNode;
|
||||
|
||||
/**
|
||||
* Describes a text attribute that can only contains some predefined values.
|
||||
* It is displayed by a {@link UiListAttributeNode}.
|
||||
*/
|
||||
public class EnumAttributeDescriptor extends ListAttributeDescriptor {
|
||||
|
||||
public EnumAttributeDescriptor(String xmlLocalName, String uiName, String nsUri,
|
||||
String tooltip) {
|
||||
super(xmlLocalName, uiName, nsUri, tooltip);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A new {@link UiListAttributeNode} linked to this descriptor.
|
||||
*/
|
||||
@Override
|
||||
public UiAttributeNode createUiNode(UiElementNode uiParent) {
|
||||
return new UiListAttributeNode(this, uiParent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.editors.descriptors;
|
||||
|
||||
import com.android.ide.eclipse.editors.ui.FlagValueCellEditor;
|
||||
import com.android.ide.eclipse.editors.uimodel.UiAttributeNode;
|
||||
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
|
||||
import com.android.ide.eclipse.editors.uimodel.UiFlagAttributeNode;
|
||||
import com.android.ide.eclipse.editors.uimodel.UiListAttributeNode;
|
||||
|
||||
import org.eclipse.jface.viewers.CellEditor;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
|
||||
/**
|
||||
* Describes a text attribute that can only contains some predefined values.
|
||||
* It is displayed by a {@link UiListAttributeNode}.
|
||||
*
|
||||
* Note: in Android resources, a "flag" is a list of fixed values where one or
|
||||
* more values can be selected using an "or", e.g. "align='left|top'".
|
||||
* By contrast, an "enum" is a list of fixed values of which only one can be
|
||||
* selected at a given time, e.g. "gravity='right'".
|
||||
* <p/>
|
||||
* This class handles the "flag" case.
|
||||
* The "enum" case is done using {@link ListAttributeDescriptor}.
|
||||
*/
|
||||
public class FlagAttributeDescriptor extends TextAttributeDescriptor {
|
||||
|
||||
private String[] mNames;
|
||||
|
||||
/**
|
||||
* Creates a new {@link FlagAttributeDescriptor} which automatically gets its
|
||||
* values from the FrameworkResourceManager.
|
||||
*/
|
||||
public FlagAttributeDescriptor(String xmlLocalName, String uiName, String nsUri,
|
||||
String tooltip) {
|
||||
super(xmlLocalName, uiName, nsUri, tooltip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link FlagAttributeDescriptor} which uses the provided values.
|
||||
*/
|
||||
public FlagAttributeDescriptor(String xmlLocalName, String uiName, String nsUri,
|
||||
String tooltip, String[] names) {
|
||||
super(xmlLocalName, uiName, nsUri, tooltip);
|
||||
mNames = names;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The initial names of the flags. Can be null, in which case the Framework
|
||||
* resource parser should be checked.
|
||||
*/
|
||||
public String[] getNames() {
|
||||
return mNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A new {@link UiListAttributeNode} linked to this descriptor.
|
||||
*/
|
||||
@Override
|
||||
public UiAttributeNode createUiNode(UiElementNode uiParent) {
|
||||
return new UiFlagAttributeNode(this, uiParent);
|
||||
}
|
||||
|
||||
// ------- IPropertyDescriptor Methods
|
||||
|
||||
@Override
|
||||
public CellEditor createPropertyEditor(Composite parent) {
|
||||
return new FlagValueCellEditor(parent);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.editors.descriptors;
|
||||
|
||||
public interface IDescriptorProvider {
|
||||
|
||||
ElementDescriptor[] getRootElementDescriptors();
|
||||
|
||||
ElementDescriptor getDescriptor();
|
||||
}
|
||||