Merge commit 'remotes/korg/cupcake' into cupcake_to_master
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
<classpathentry kind="lib" path="layoutlib_api.jar"/>
|
||||
<classpathentry kind="lib" path="layoutlib_utils.jar"/>
|
||||
<classpathentry kind="lib" path="ninepatch.jar"/>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/SdkUiLib"/>
|
||||
<classpathentry kind="lib" path="sdklib.jar" sourcepath="/SdkLib"/>
|
||||
<classpathentry kind="lib" path="sdkuilib.jar" sourcepath="/SdkUiLib"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
|
||||
@@ -39,10 +39,12 @@ Require-Bundle: com.android.ide.eclipse.ddms,
|
||||
org.eclipse.wst.sse.core,
|
||||
org.eclipse.wst.sse.ui,
|
||||
org.eclipse.wst.xml.core,
|
||||
org.eclipse.wst.xml.ui
|
||||
org.eclipse.wst.xml.ui,
|
||||
org.eclipse.jdt.junit
|
||||
Eclipse-LazyStart: true
|
||||
Export-Package: com.android.ide.eclipse.adt,
|
||||
com.android.ide.eclipse.adt.build;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.adt.launch;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.adt.project;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.adt.project.internal;x-friends:="com.android.ide.eclipse.tests",
|
||||
com.android.ide.eclipse.adt.sdk;x-friends:="com.android.ide.eclipse.tests",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 664 B |
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
@@ -17,6 +17,14 @@
|
||||
<super type="org.eclipse.core.resources.textmarker"/>
|
||||
<persistent value="true"/>
|
||||
</extension>
|
||||
<extension
|
||||
id="com.android.ide.eclipse.common.aapt2Problem"
|
||||
name="Android AAPT Problem"
|
||||
point="org.eclipse.core.resources.markers">
|
||||
<super type="org.eclipse.core.resources.problemmarker"/>
|
||||
<super type="org.eclipse.core.resources.textmarker"/>
|
||||
<persistent value="true"/>
|
||||
</extension>
|
||||
<extension
|
||||
id="com.android.ide.eclipse.common.aidlProblem"
|
||||
name="Android AIDL Problem"
|
||||
@@ -103,7 +111,7 @@
|
||||
<extension
|
||||
point="org.eclipse.debug.core.launchConfigurationTypes">
|
||||
<launchConfigurationType
|
||||
delegate="com.android.ide.eclipse.adt.debug.launching.LaunchConfigDelegate"
|
||||
delegate="com.android.ide.eclipse.adt.launch.LaunchConfigDelegate"
|
||||
delegateDescription="The Android Application Launcher supports running and debugging remote Android applications on devices or emulators."
|
||||
delegateName="Android Launcher"
|
||||
id="com.android.ide.eclipse.adt.debug.LaunchConfigType"
|
||||
@@ -124,7 +132,7 @@
|
||||
<extension
|
||||
point="org.eclipse.debug.ui.launchConfigurationTabGroups">
|
||||
<launchConfigurationTabGroup
|
||||
class="com.android.ide.eclipse.adt.debug.ui.LaunchConfigTabGroup"
|
||||
class="com.android.ide.eclipse.adt.launch.LaunchConfigTabGroup"
|
||||
description="Android Application"
|
||||
id="com.android.ide.eclipse.adt.debug.LaunchConfigTabGroup"
|
||||
type="com.android.ide.eclipse.adt.debug.LaunchConfigType"/>
|
||||
@@ -132,8 +140,8 @@
|
||||
<extension
|
||||
point="org.eclipse.debug.ui.launchShortcuts">
|
||||
<shortcut
|
||||
category="com.android.ide.eclipse.adt.debug.LaunchConfigType"
|
||||
class="com.android.ide.eclipse.adt.debug.launching.LaunchShortcut"
|
||||
category="com.android.ide.eclipse.adt.launch.LaunchConfigType"
|
||||
class="com.android.ide.eclipse.adt.launch.LaunchShortcut"
|
||||
icon="icons/android.png"
|
||||
id="com.android.ide.eclipse.adt.debug.launching.LaunchShortcut"
|
||||
label="Android Application"
|
||||
@@ -211,12 +219,6 @@
|
||||
name="projectNature"
|
||||
value="com.android.ide.eclipse.adt.AndroidNature">
|
||||
</filter>
|
||||
<action
|
||||
class="com.android.ide.eclipse.adt.project.CreateAidlImportAction"
|
||||
enablesFor="1"
|
||||
id="com.android.ide.eclipse.adt.project.CreateAidlImportAction"
|
||||
label="Create Aidl preprocess file for Parcelable classes"
|
||||
menubarPath="com.android.ide.eclipse.adt.AndroidTools/group1"/>
|
||||
<action
|
||||
class="com.android.ide.eclipse.adt.project.NewXmlFileWizardAction"
|
||||
enablesFor="1"
|
||||
@@ -290,7 +292,7 @@
|
||||
<perspectiveExtension targetID="org.eclipse.jdt.ui.JavaPerspective">
|
||||
<newWizardShortcut id="com.android.ide.eclipse.adt.project.NewProjectWizard" />
|
||||
<newWizardShortcut
|
||||
id="com.android.ide.eclipse.adt.wizards.NewXmlFileWizard">
|
||||
id="com.android.ide.eclipse.editors.wizards.NewXmlFileWizard">
|
||||
</newWizardShortcut>
|
||||
</perspectiveExtension>
|
||||
<perspectiveExtension targetID="org.eclipse.debug.ui.DebugPerspective">
|
||||
@@ -337,24 +339,24 @@
|
||||
name="Debug Android Application"
|
||||
description="Debug Android Application"
|
||||
categoryId="org.eclipse.debug.ui.category.run"
|
||||
id="com.android.ide.eclipse.adt.debug.launching.LaunchShortcut.debug">
|
||||
id="com.android.ide.eclipse.adt.launch.LaunchShortcut.debug">
|
||||
</command>
|
||||
<command
|
||||
name="Run Android Application"
|
||||
description="Run Android Application"
|
||||
categoryId="org.eclipse.debug.ui.category.run"
|
||||
id="com.android.ide.eclipse.adt.debug.launching.LaunchShortcut.run">
|
||||
id="com.android.ide.eclipse.adt.launch.LaunchShortcut.run">
|
||||
</command>
|
||||
<keyBinding
|
||||
keySequence="M3+M2+A D"
|
||||
contextId="org.eclipse.ui.globalScope"
|
||||
commandId="com.android.ide.eclipse.adt.debug.launching.LaunchShortcut.debug"
|
||||
commandId="com.android.ide.eclipse.adt.launch.LaunchShortcut.debug"
|
||||
keyConfigurationId="org.eclipse.ui.defaultAcceleratorConfiguration">
|
||||
</keyBinding>
|
||||
<keyBinding
|
||||
keySequence="M3+M2+A R"
|
||||
contextId="org.eclipse.ui.globalScope"
|
||||
commandId="com.android.ide.eclipse.adt.debug.launching.LaunchShortcut.run"
|
||||
commandId="com.android.ide.eclipse.adt.launch.LaunchShortcut.run"
|
||||
keyConfigurationId="org.eclipse.ui.defaultAcceleratorConfiguration">
|
||||
</keyBinding>
|
||||
</extension>
|
||||
@@ -464,4 +466,48 @@
|
||||
</enabledWhen>
|
||||
</page>
|
||||
</extension>
|
||||
<extension
|
||||
point="org.eclipse.ui.actionSets">
|
||||
<actionSet
|
||||
description="Android Wizards"
|
||||
id="adt.actionSet1"
|
||||
label="Android Wizards"
|
||||
visible="true">
|
||||
<action
|
||||
class="com.android.ide.eclipse.adt.wizards.actions.NewXmlFileAction"
|
||||
icon="icons/new_xml.png"
|
||||
id="com.android.ide.eclipse.adt.wizards.actions.NewXmlFileAction"
|
||||
label="New Android XML File"
|
||||
style="push"
|
||||
toolbarPath="android_project"
|
||||
tooltip="Opens a wizard to help create a new Android XML file">
|
||||
<enablement>
|
||||
<objectState
|
||||
name="projectNature"
|
||||
value="com.android.ide.eclipse.adt.AndroidNature">
|
||||
</objectState>
|
||||
</enablement>
|
||||
</action>
|
||||
<action
|
||||
class="com.android.ide.eclipse.adt.wizards.actions.NewProjectAction"
|
||||
icon="icons/new_adt_project.png"
|
||||
id="com.android.ide.eclipse.adt.wizards.actions.NewProjectAction"
|
||||
label="New Android Project"
|
||||
style="push"
|
||||
toolbarPath="android_project"
|
||||
tooltip="Opens a wizard to help create a new Android project">
|
||||
</action>
|
||||
</actionSet>
|
||||
</extension>
|
||||
<extension
|
||||
point="org.eclipse.debug.core.launchDelegates">
|
||||
<launchDelegate
|
||||
delegate="com.android.ide.eclipse.adt.launch.JUnitLaunchConfigDelegate"
|
||||
delegateDescription="Removes the Android JAR from the Bootstrap Classpath"
|
||||
id="com.android.ide.eclipse.adt.launch.JUnitLaunchConfigDelegate.launchAndroidJunit"
|
||||
modes="run,debug"
|
||||
name="Android JUnit"
|
||||
type="org.eclipse.jdt.junit.launchconfig">
|
||||
</launchDelegate>
|
||||
</extension>
|
||||
</plugin>
|
||||
|
||||
@@ -20,8 +20,7 @@ import com.android.ddmuilib.StackTracePanel;
|
||||
import com.android.ddmuilib.StackTracePanel.ISourceRevealer;
|
||||
import com.android.ddmuilib.console.DdmConsole;
|
||||
import com.android.ddmuilib.console.IDdmConsole;
|
||||
import com.android.ide.eclipse.adt.build.DexWrapper;
|
||||
import com.android.ide.eclipse.adt.debug.launching.AndroidLaunchController;
|
||||
import com.android.ide.eclipse.adt.launch.AndroidLaunchController;
|
||||
import com.android.ide.eclipse.adt.preferences.BuildPreferencePage;
|
||||
import com.android.ide.eclipse.adt.project.ProjectHelper;
|
||||
import com.android.ide.eclipse.adt.project.export.ExportWizard;
|
||||
@@ -29,6 +28,7 @@ import com.android.ide.eclipse.adt.project.internal.AndroidClasspathContainerIni
|
||||
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.adt.sdk.Sdk.ITargetChangeListener;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.common.EclipseUiHelper;
|
||||
import com.android.ide.eclipse.common.SdkStatsHelper;
|
||||
@@ -166,11 +166,16 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
/** 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>();
|
||||
* Any access MUST be in a synchronized(mPostLoadProjectsToResolve) block */
|
||||
private final ArrayList<IJavaProject> mPostLoadProjectsToResolve =
|
||||
new ArrayList<IJavaProject>();
|
||||
/** Project to check validity of cache vs actual once the SDK is loaded.
|
||||
* Any access MUST be in a synchronized(mPostLoadProjectsToResolve) block */
|
||||
private final ArrayList<IJavaProject> mPostLoadProjectsToCheck = new ArrayList<IJavaProject>();
|
||||
|
||||
private ResourceMonitor mResourceMonitor;
|
||||
private ArrayList<Runnable> mResourceRefreshListener = new ArrayList<Runnable>();
|
||||
private ArrayList<ITargetChangeListener> mTargetChangeListeners =
|
||||
new ArrayList<ITargetChangeListener>();
|
||||
|
||||
/**
|
||||
* Custom PrintStream for Dx output. This class overrides the method
|
||||
@@ -306,12 +311,12 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
if (checkSdkLocationAndId()) {
|
||||
// if sdk if valid, reparse it
|
||||
|
||||
// add the current Android project to the list of projects to be updated
|
||||
// add all the opened Android projects to the list of projects to be updated
|
||||
// after the SDK is reloaded
|
||||
synchronized (mPostLoadProjects) {
|
||||
synchronized (getSdkLockObject()) {
|
||||
// get the project to refresh.
|
||||
IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects();
|
||||
mPostLoadProjects.addAll(Arrays.asList(androidProjects));
|
||||
mPostLoadProjectsToResolve.addAll(Arrays.asList(androidProjects));
|
||||
}
|
||||
|
||||
// parse the SDK resources at the new location
|
||||
@@ -419,8 +424,6 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
|
||||
stopEditors();
|
||||
|
||||
DexWrapper.unloadDex();
|
||||
|
||||
mRed.dispose();
|
||||
synchronized (AdtPlugin.class) {
|
||||
sPlugin = null;
|
||||
@@ -461,21 +464,11 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
return SdkConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_ADB;
|
||||
}
|
||||
|
||||
/** Returns the aapt path relative to the sdk folder */
|
||||
public static String getOsRelativeAapt() {
|
||||
return SdkConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_AAPT;
|
||||
}
|
||||
|
||||
/** Returns the emulator path relative to the sdk folder */
|
||||
public static String getOsRelativeEmulator() {
|
||||
return SdkConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_EMULATOR;
|
||||
}
|
||||
|
||||
/** Returns the aidl path relative to the sdk folder */
|
||||
public static String getOsRelativeAidl() {
|
||||
return SdkConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_AIDL;
|
||||
}
|
||||
|
||||
/** Returns the absolute adb path */
|
||||
public static String getOsAbsoluteAdb() {
|
||||
return getOsSdkFolder() + getOsRelativeAdb();
|
||||
@@ -487,21 +480,11 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
AndroidConstants.FN_TRACEVIEW;
|
||||
}
|
||||
|
||||
/** Returns the absolute aapt path */
|
||||
public static String getOsAbsoluteAapt() {
|
||||
return getOsSdkFolder() + getOsRelativeAapt();
|
||||
}
|
||||
|
||||
/** Returns the absolute emulator path */
|
||||
public static String getOsAbsoluteEmulator() {
|
||||
return getOsSdkFolder() + getOsRelativeEmulator();
|
||||
}
|
||||
|
||||
/** Returns the absolute aidl path */
|
||||
public static String getOsAbsoluteAidl() {
|
||||
return getOsSdkFolder() + getOsRelativeAidl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Url file path to the javaDoc folder.
|
||||
*/
|
||||
@@ -869,19 +852,43 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Returns whether the Sdk has been loaded.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
public final LoadStatus getSdkLoadStatus() {
|
||||
synchronized (getSdkLockObject()) {
|
||||
return mSdkIsLoaded;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the lock object for SDK loading. If you wish to do things while the SDK is loading,
|
||||
* you must synchronize on this object.
|
||||
*/
|
||||
public final Object getSdkLockObject() {
|
||||
return mPostLoadProjectsToResolve;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given {@link IJavaProject} to have its target resolved again once the SDK finishes
|
||||
* to load.
|
||||
*/
|
||||
public final void setProjectToResolve(IJavaProject javaProject) {
|
||||
synchronized (getSdkLockObject()) {
|
||||
mPostLoadProjectsToResolve.add(javaProject);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given {@link IJavaProject} to have its target checked for consistency
|
||||
* once the SDK finishes to load. This is used if the target is resolved using cached
|
||||
* information while the SDK is loading.
|
||||
*/
|
||||
public final void setProjectToCheck(IJavaProject javaProject) {
|
||||
// only lock on
|
||||
synchronized (getSdkLockObject()) {
|
||||
mPostLoadProjectsToCheck.add(javaProject);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the location of the SDK is valid and if it is, grab the SDK API version
|
||||
@@ -939,8 +946,6 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
// check the path to various tools we use
|
||||
String[] filesToCheck = new String[] {
|
||||
osSdkLocation + getOsRelativeAdb(),
|
||||
osSdkLocation + getOsRelativeAapt(),
|
||||
osSdkLocation + getOsRelativeAidl(),
|
||||
osSdkLocation + getOsRelativeEmulator()
|
||||
};
|
||||
for (String file : filesToCheck) {
|
||||
@@ -982,7 +987,7 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
Constants.BUNDLE_VERSION);
|
||||
Version version = new Version(versionString);
|
||||
|
||||
SdkStatsHelper.pingUsageServer("editors", version); //$NON-NLS-1$
|
||||
SdkStatsHelper.pingUsageServer("adt", version); //$NON-NLS-1$
|
||||
|
||||
return Status.OK_STATUS;
|
||||
} catch (Throwable t) {
|
||||
@@ -1015,61 +1020,69 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
|
||||
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();
|
||||
int n = sdk.getTargets().length;
|
||||
if (n > 0) {
|
||||
int w = 60 / n;
|
||||
for (IAndroidTarget target : sdk.getTargets()) {
|
||||
SubMonitor p2 = progress.newChild(w);
|
||||
IStatus status = new AndroidTargetParser(target).run(p2);
|
||||
if (status.getCode() != IStatus.OK) {
|
||||
synchronized (getSdkLockObject()) {
|
||||
mSdkIsLoaded = LoadStatus.FAILED;
|
||||
mPostLoadProjectsToResolve.clear();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
synchronized (mPostLoadProjects) {
|
||||
synchronized (getSdkLockObject()) {
|
||||
mSdkIsLoaded = LoadStatus.LOADED;
|
||||
|
||||
progress.setTaskName("Check Projects");
|
||||
|
||||
// check the projects that need checking.
|
||||
// The method modifies the list (it removes the project that
|
||||
// do not need to be resolved again).
|
||||
AndroidClasspathContainerInitializer.checkProjectsCache(
|
||||
mPostLoadProjectsToCheck);
|
||||
|
||||
mPostLoadProjectsToResolve.addAll(mPostLoadProjectsToCheck);
|
||||
|
||||
// update the project that needs recompiling.
|
||||
if (mPostLoadProjects.size() > 0) {
|
||||
IJavaProject[] array = mPostLoadProjects.toArray(
|
||||
new IJavaProject[mPostLoadProjects.size()]);
|
||||
if (mPostLoadProjectsToResolve.size() > 0) {
|
||||
IJavaProject[] array = mPostLoadProjectsToResolve.toArray(
|
||||
new IJavaProject[mPostLoadProjectsToResolve.size()]);
|
||||
AndroidClasspathContainerInitializer.updateProjects(array);
|
||||
mPostLoadProjects.clear();
|
||||
mPostLoadProjectsToResolve.clear();
|
||||
}
|
||||
|
||||
progress.worked(10);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify resource changed listeners
|
||||
progress.subTask("Refresh UI");
|
||||
progress.setWorkRemaining(mResourceRefreshListener.size());
|
||||
progress.setTaskName("Refresh UI");
|
||||
progress.setWorkRemaining(mTargetChangeListeners.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);
|
||||
final List<ITargetChangeListener> listeners =
|
||||
(List<ITargetChangeListener>)mTargetChangeListeners.clone();
|
||||
final SubMonitor progress2 = progress;
|
||||
AdtPlugin.getDisplay().syncExec(new Runnable() {
|
||||
public void run() {
|
||||
for (ITargetChangeListener listener : listeners) {
|
||||
try {
|
||||
listener.onTargetsLoaded();
|
||||
} catch (Exception e) {
|
||||
AdtPlugin.log(e, "Failed to update a TargetChangeListener."); //$NON-NLS-1$
|
||||
} finally {
|
||||
progress2.worked(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
if (monitor != null) {
|
||||
monitor.done();
|
||||
@@ -1315,12 +1328,42 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
}, IResourceDelta.ADDED | IResourceDelta.CHANGED);
|
||||
}
|
||||
|
||||
public void addResourceChangedListener(Runnable resourceRefreshListener) {
|
||||
mResourceRefreshListener.add(resourceRefreshListener);
|
||||
/**
|
||||
* Adds a new {@link ITargetChangeListener} to be notified when a new SDK is loaded, or when
|
||||
* a project has its target changed.
|
||||
*/
|
||||
public void addTargetListener(ITargetChangeListener listener) {
|
||||
mTargetChangeListeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeResourceChangedListener(Runnable resourceRefreshListener) {
|
||||
mResourceRefreshListener.remove(resourceRefreshListener);
|
||||
/**
|
||||
* Removes an existing {@link ITargetChangeListener}.
|
||||
* @see #addTargetListener(ITargetChangeListener)
|
||||
*/
|
||||
public void removeTargetListener(ITargetChangeListener listener) {
|
||||
mTargetChangeListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all the {@link ITargetChangeListener} that a target has changed for a given project.
|
||||
* <p/>Only editors related to that project should reload.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void updateTargetListener(final IProject project) {
|
||||
final List<ITargetChangeListener> listeners =
|
||||
(List<ITargetChangeListener>)mTargetChangeListeners.clone();
|
||||
|
||||
AdtPlugin.getDisplay().asyncExec(new Runnable() {
|
||||
public void run() {
|
||||
for (ITargetChangeListener listener : listeners) {
|
||||
try {
|
||||
listener.onProjectTargetChange(project);
|
||||
} catch (Exception e) {
|
||||
AdtPlugin.log(e, "Failed to update a TargetChangeListener."); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static synchronized OutputStream getErrorStream() {
|
||||
|
||||
@@ -19,7 +19,7 @@ package com.android.ide.eclipse.adt.build;
|
||||
import com.android.ide.eclipse.adt.AdtConstants;
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.project.ProjectHelper;
|
||||
import com.android.ide.eclipse.adt.sdk.LoadStatus;
|
||||
import com.android.ide.eclipse.adt.sdk.AndroidTargetData;
|
||||
import com.android.ide.eclipse.adt.sdk.Sdk;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.common.project.BaseProjectHelper;
|
||||
@@ -67,6 +67,8 @@ import java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class ApkBuilder extends BaseBuilder {
|
||||
|
||||
@@ -181,7 +183,7 @@ public class ApkBuilder extends BaseBuilder {
|
||||
return mMakeFinalPackage;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link IZipEntryFilter} to filter out everything that is not a standard java resources.
|
||||
* <p/>Used in {@link SignedJarBuilder#writeZip(java.io.InputStream, IZipEntryFilter)} when
|
||||
@@ -201,6 +203,9 @@ public class ApkBuilder extends BaseBuilder {
|
||||
// get a project object
|
||||
IProject project = getProject();
|
||||
|
||||
// Top level check to make sure the build can move forward.
|
||||
abortOnBadSetup(project);
|
||||
|
||||
// get the list of referenced projects.
|
||||
IProject[] referencedProjects = ProjectHelper.getReferencedProjects(project);
|
||||
IJavaProject[] referencedJavaProjects = getJavaProjects(referencedProjects);
|
||||
@@ -215,6 +220,7 @@ public class ApkBuilder extends BaseBuilder {
|
||||
|
||||
// First thing we do is go through the resource delta to not
|
||||
// lose it if we have to abort the build for any reason.
|
||||
ApkDeltaVisitor dv = null;
|
||||
if (kind == FULL_BUILD) {
|
||||
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
|
||||
Messages.Start_Full_Apk_Build);
|
||||
@@ -233,22 +239,13 @@ public class ApkBuilder extends BaseBuilder {
|
||||
mConvertToDex = true;
|
||||
mBuildFinalPackage = true;
|
||||
} else {
|
||||
ApkDeltaVisitor dv = new ApkDeltaVisitor(this, sourceList, outputFolder);
|
||||
dv = new ApkDeltaVisitor(this, sourceList, outputFolder);
|
||||
delta.accept(dv);
|
||||
|
||||
// save the state
|
||||
mPackageResources |= dv.getPackageResources();
|
||||
mConvertToDex |= dv.getConvertToDex();
|
||||
mBuildFinalPackage |= dv.getMakeFinalPackage();
|
||||
|
||||
if (dv.mXmlError) {
|
||||
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
|
||||
Messages.Xml_Error);
|
||||
|
||||
// if there was some XML errors, we just return w/o doing
|
||||
// anything since we've put some markers in the files anyway
|
||||
return referencedProjects;
|
||||
}
|
||||
}
|
||||
|
||||
// also go through the delta for all the referenced projects, until we are forced to
|
||||
@@ -258,78 +255,28 @@ public class ApkBuilder extends BaseBuilder {
|
||||
IJavaProject referencedJavaProject = referencedJavaProjects[i];
|
||||
delta = getDelta(referencedJavaProject.getProject());
|
||||
if (delta != null) {
|
||||
ReferencedProjectDeltaVisitor dv = new ReferencedProjectDeltaVisitor(
|
||||
ReferencedProjectDeltaVisitor refProjectDv = new ReferencedProjectDeltaVisitor(
|
||||
referencedJavaProject);
|
||||
delta.accept(dv);
|
||||
delta.accept(refProjectDv);
|
||||
|
||||
// save the state
|
||||
mConvertToDex |= dv.needDexConvertion();
|
||||
mBuildFinalPackage |= dv.needMakeFinalPackage();
|
||||
mConvertToDex |= refProjectDv.needDexConvertion();
|
||||
mBuildFinalPackage |= refProjectDv.needMakeFinalPackage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// do some extra check, in case the output files are not present. This
|
||||
// will force to recreate them.
|
||||
IResource tmp = null;
|
||||
|
||||
if (mPackageResources == false && outputFolder != null) {
|
||||
tmp = outputFolder.findMember(AndroidConstants.FN_RESOURCES_AP_);
|
||||
if (tmp == null || tmp.exists() == false) {
|
||||
mPackageResources = true;
|
||||
mBuildFinalPackage = true;
|
||||
}
|
||||
}
|
||||
if (mConvertToDex == false && outputFolder != null) {
|
||||
tmp = outputFolder.findMember(AndroidConstants.FN_CLASSES_DEX);
|
||||
if (tmp == null || tmp.exists() == false) {
|
||||
mConvertToDex = true;
|
||||
mBuildFinalPackage = true;
|
||||
}
|
||||
}
|
||||
|
||||
// also check the final file!
|
||||
String finalPackageName = project.getName() + AndroidConstants.DOT_ANDROID_PACKAGE;
|
||||
if (mBuildFinalPackage == false && outputFolder != null) {
|
||||
tmp = outputFolder.findMember(finalPackageName);
|
||||
if (tmp == null || (tmp instanceof IFile &&
|
||||
tmp.exists() == false)) {
|
||||
String msg = String.format(Messages.s_Missing_Repackaging, finalPackageName);
|
||||
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg);
|
||||
mBuildFinalPackage = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// store the build status in the persistent storage
|
||||
saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX , mConvertToDex);
|
||||
saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
|
||||
saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);
|
||||
|
||||
// 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())
|
||||
!= ProjectHelper.COMPILER_COMPLIANCE_OK) {
|
||||
return referencedProjects;
|
||||
}
|
||||
|
||||
// now check if the project has problem marker already
|
||||
if (ProjectHelper.hasError(project, true)) {
|
||||
// we found a marker with error severity: we abort the build.
|
||||
// Since this is going to happen every time we save a file while
|
||||
// errors are remaining, we do not force the display of the console, which
|
||||
// would, in most cases, show on top of the Problem view (which is more
|
||||
// important in that case).
|
||||
if (dv != null && dv.mXmlError) {
|
||||
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
|
||||
Messages.Project_Has_Errors);
|
||||
Messages.Xml_Error);
|
||||
|
||||
// if there was some XML errors, we just return w/o doing
|
||||
// anything since we've put some markers in the files anyway
|
||||
return referencedProjects;
|
||||
}
|
||||
|
||||
@@ -350,6 +297,82 @@ public class ApkBuilder extends BaseBuilder {
|
||||
return referencedProjects;
|
||||
}
|
||||
|
||||
// get the extra configs for the project.
|
||||
// The map contains (name, filter) where 'name' is a name to be used in the apk filename,
|
||||
// and filter is the resource filter to be used in the aapt -c parameters to restrict
|
||||
// which resource configurations to package in the apk.
|
||||
Map<String, String> configs = Sdk.getCurrent().getProjectApkConfigs(project);
|
||||
|
||||
// do some extra check, in case the output files are not present. This
|
||||
// will force to recreate them.
|
||||
IResource tmp = null;
|
||||
|
||||
if (mPackageResources == false) {
|
||||
// check the full resource package
|
||||
tmp = outputFolder.findMember(AndroidConstants.FN_RESOURCES_AP_);
|
||||
if (tmp == null || tmp.exists() == false) {
|
||||
mPackageResources = true;
|
||||
mBuildFinalPackage = true;
|
||||
} else {
|
||||
// if the full package is present, we check the filtered resource packages as well
|
||||
if (configs != null) {
|
||||
Set<Entry<String, String>> entrySet = configs.entrySet();
|
||||
|
||||
for (Entry<String, String> entry : entrySet) {
|
||||
String filename = String.format(AndroidConstants.FN_RESOURCES_S_AP_,
|
||||
entry.getKey());
|
||||
|
||||
tmp = outputFolder.findMember(filename);
|
||||
if (tmp == null || (tmp instanceof IFile &&
|
||||
tmp.exists() == false)) {
|
||||
String msg = String.format(Messages.s_Missing_Repackaging, filename);
|
||||
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg);
|
||||
mPackageResources = true;
|
||||
mBuildFinalPackage = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check classes.dex is present. If not we force to recreate it.
|
||||
if (mConvertToDex == false) {
|
||||
tmp = outputFolder.findMember(AndroidConstants.FN_CLASSES_DEX);
|
||||
if (tmp == null || tmp.exists() == false) {
|
||||
mConvertToDex = true;
|
||||
mBuildFinalPackage = true;
|
||||
}
|
||||
}
|
||||
|
||||
// also check the final file(s)!
|
||||
String finalPackageName = ProjectHelper.getApkFilename(project, null /*config*/);
|
||||
if (mBuildFinalPackage == false) {
|
||||
tmp = outputFolder.findMember(finalPackageName);
|
||||
if (tmp == null || (tmp instanceof IFile &&
|
||||
tmp.exists() == false)) {
|
||||
String msg = String.format(Messages.s_Missing_Repackaging, finalPackageName);
|
||||
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg);
|
||||
mBuildFinalPackage = true;
|
||||
} else if (configs != null) {
|
||||
// if the full apk is present, we check the filtered apk as well
|
||||
Set<Entry<String, String>> entrySet = configs.entrySet();
|
||||
|
||||
for (Entry<String, String> entry : entrySet) {
|
||||
String filename = ProjectHelper.getApkFilename(project, entry.getKey());
|
||||
|
||||
tmp = outputFolder.findMember(filename);
|
||||
if (tmp == null || (tmp instanceof IFile &&
|
||||
tmp.exists() == false)) {
|
||||
String msg = String.format(Messages.s_Missing_Repackaging, filename);
|
||||
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg);
|
||||
mBuildFinalPackage = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// at this point we know if we need to recreate the temporary apk
|
||||
// or the dex file, but we don't know if we simply need to recreate them
|
||||
// because they are missing
|
||||
@@ -381,9 +404,23 @@ public class ApkBuilder extends BaseBuilder {
|
||||
// handle already present .apk, and if that one failed as well, the user will be
|
||||
// notified.
|
||||
finalPackage.delete();
|
||||
|
||||
if (configs != null) {
|
||||
Set<Entry<String, String>> entrySet = configs.entrySet();
|
||||
for (Entry<String, String> entry : entrySet) {
|
||||
String packageFilepath = osBinPath + File.separator +
|
||||
ProjectHelper.getApkFilename(project, entry.getKey());
|
||||
|
||||
finalPackage = new File(packageFilepath);
|
||||
finalPackage.delete();
|
||||
}
|
||||
}
|
||||
|
||||
// first we check if we need to package the resources.
|
||||
if (mPackageResources) {
|
||||
// remove some aapt_package only markers.
|
||||
removeMarkersFromContainer(project, AndroidConstants.MARKER_AAPT_PACKAGE);
|
||||
|
||||
// need to figure out some path before we can execute aapt;
|
||||
|
||||
// resource to the AndroidManifest.xml file
|
||||
@@ -424,13 +461,30 @@ public class ApkBuilder extends BaseBuilder {
|
||||
osAssetsPath = assetsFolder.getLocation().toOSString();
|
||||
}
|
||||
|
||||
// build the default resource package
|
||||
if (executeAapt(project, osManifestPath, osResPath,
|
||||
osAssetsPath, osBinPath + File.separator +
|
||||
AndroidConstants.FN_RESOURCES_AP_) == false) {
|
||||
AndroidConstants.FN_RESOURCES_AP_, null /*configFilter*/) == false) {
|
||||
// aapt failed. Whatever files that needed to be marked
|
||||
// have already been marked. We just return.
|
||||
return referencedProjects;
|
||||
}
|
||||
|
||||
// now do the same thing for all the configured resource packages.
|
||||
if (configs != null) {
|
||||
Set<Entry<String, String>> entrySet = configs.entrySet();
|
||||
for (Entry<String, String> entry : entrySet) {
|
||||
String outPathFormat = osBinPath + File.separator +
|
||||
AndroidConstants.FN_RESOURCES_S_AP_;
|
||||
String outPath = String.format(outPathFormat, entry.getKey());
|
||||
if (executeAapt(project, osManifestPath, osResPath,
|
||||
osAssetsPath, outPath, entry.getValue()) == false) {
|
||||
// aapt failed. Whatever files that needed to be marked
|
||||
// have already been marked. We just return.
|
||||
return referencedProjects;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// build has been done. reset the state of the builder
|
||||
mPackageResources = false;
|
||||
@@ -456,25 +510,49 @@ public class ApkBuilder extends BaseBuilder {
|
||||
}
|
||||
|
||||
// now we need to make the final package from the intermediary apk
|
||||
// and classes.dex
|
||||
// and classes.dex.
|
||||
// This is the default package with all the resources.
|
||||
|
||||
String classesDexPath = osBinPath + File.separator + AndroidConstants.FN_CLASSES_DEX;
|
||||
if (finalPackage(osBinPath + File.separator + AndroidConstants.FN_RESOURCES_AP_,
|
||||
osBinPath + File.separator + AndroidConstants.FN_CLASSES_DEX,
|
||||
osFinalPackagePath, javaProject, referencedJavaProjects) == false) {
|
||||
classesDexPath,osFinalPackagePath, javaProject,
|
||||
referencedJavaProjects) == false) {
|
||||
return referencedProjects;
|
||||
} else {
|
||||
// get the resource to bin
|
||||
outputFolder.refreshLocal(IResource.DEPTH_ONE, monitor);
|
||||
|
||||
// build has been done. reset the state of the builder
|
||||
mBuildFinalPackage = false;
|
||||
|
||||
// and store it
|
||||
saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);
|
||||
|
||||
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(),
|
||||
"Build Success!");
|
||||
}
|
||||
|
||||
// now do the same thing for all the configured resource packages.
|
||||
if (configs != null) {
|
||||
String resPathFormat = osBinPath + File.separator +
|
||||
AndroidConstants.FN_RESOURCES_S_AP_;
|
||||
|
||||
Set<Entry<String, String>> entrySet = configs.entrySet();
|
||||
for (Entry<String, String> entry : entrySet) {
|
||||
// make the filename for the resource package.
|
||||
String resPath = String.format(resPathFormat, entry.getKey());
|
||||
|
||||
// make the filename for the apk to generate
|
||||
String apkOsFilePath = osBinPath + File.separator +
|
||||
ProjectHelper.getApkFilename(project, entry.getKey());
|
||||
if (finalPackage(resPath, classesDexPath, apkOsFilePath, javaProject,
|
||||
referencedJavaProjects) == false) {
|
||||
return referencedProjects;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we are done.
|
||||
|
||||
// get the resource to bin
|
||||
outputFolder.refreshLocal(IResource.DEPTH_ONE, monitor);
|
||||
|
||||
// build has been done. reset the state of the builder
|
||||
mBuildFinalPackage = false;
|
||||
|
||||
// and store it
|
||||
saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);
|
||||
|
||||
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(),
|
||||
"Build Success!");
|
||||
}
|
||||
return referencedProjects;
|
||||
}
|
||||
@@ -498,19 +576,27 @@ public class ApkBuilder extends BaseBuilder {
|
||||
* @param osResPath The path to the res folder
|
||||
* @param osAssetsPath The path to the assets folder. This can be null.
|
||||
* @param osOutFilePath The path to the temporary resource file to create.
|
||||
* @param configFilter The configuration filter for the resources to include
|
||||
* (used with -c option, for example "port,en,fr" to include portrait, English and French
|
||||
* resources.)
|
||||
* @return true if success, false otherwise.
|
||||
*/
|
||||
private boolean executeAapt(IProject project, String osManifestPath,
|
||||
String osResPath, String osAssetsPath, String osOutFilePath) {
|
||||
String osResPath, String osAssetsPath, String osOutFilePath, String configFilter) {
|
||||
IAndroidTarget target = Sdk.getCurrent().getTarget(project);
|
||||
|
||||
// Create the command line.
|
||||
ArrayList<String> commandArray = new ArrayList<String>();
|
||||
commandArray.add(AdtPlugin.getOsAbsoluteAapt());
|
||||
commandArray.add(target.getPath(IAndroidTarget.AAPT));
|
||||
commandArray.add("package"); //$NON-NLS-1$
|
||||
commandArray.add("-f");//$NON-NLS-1$
|
||||
if (AdtPlugin.getBuildVerbosity() == AdtConstants.BUILD_VERBOSE) {
|
||||
commandArray.add("-v"); //$NON-NLS-1$
|
||||
}
|
||||
if (configFilter != null) {
|
||||
commandArray.add("-c"); //$NON-NLS-1$
|
||||
commandArray.add(configFilter);
|
||||
}
|
||||
commandArray.add("-M"); //$NON-NLS-1$
|
||||
commandArray.add(osManifestPath);
|
||||
commandArray.add("-S"); //$NON-NLS-1$
|
||||
@@ -520,8 +606,7 @@ public class ApkBuilder extends BaseBuilder {
|
||||
commandArray.add(osAssetsPath);
|
||||
}
|
||||
commandArray.add("-I"); //$NON-NLS-1$
|
||||
commandArray.add(
|
||||
Sdk.getCurrent().getTarget(project).getPath(IAndroidTarget.ANDROID_JAR));
|
||||
commandArray.add(target.getPath(IAndroidTarget.ANDROID_JAR));
|
||||
commandArray.add("-F"); //$NON-NLS-1$
|
||||
commandArray.add(osOutFilePath);
|
||||
|
||||
@@ -599,14 +684,19 @@ public class ApkBuilder extends BaseBuilder {
|
||||
*/
|
||||
private boolean executeDx(IJavaProject javaProject, String osBinPath, String osOutFilePath,
|
||||
IJavaProject[] referencedJavaProjects) throws CoreException {
|
||||
IAndroidTarget target = Sdk.getCurrent().getTarget(javaProject.getProject());
|
||||
AndroidTargetData targetData = Sdk.getCurrent().getTargetData(target);
|
||||
if (targetData == null) {
|
||||
throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
|
||||
Messages.ApkBuilder_UnableBuild_Dex_Not_loaded));
|
||||
}
|
||||
|
||||
// get the dex wrapper
|
||||
DexWrapper wrapper = DexWrapper.getWrapper();
|
||||
DexWrapper wrapper = targetData.getDexWrapper();
|
||||
|
||||
if (wrapper == null) {
|
||||
if (DexWrapper.getStatus() == LoadStatus.FAILED) {
|
||||
throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
|
||||
Messages.ApkBuilder_UnableBuild_Dex_Not_loaded));
|
||||
}
|
||||
throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
|
||||
Messages.ApkBuilder_UnableBuild_Dex_Not_loaded));
|
||||
}
|
||||
|
||||
// make sure dx use the proper output streams.
|
||||
@@ -876,9 +966,10 @@ public class ApkBuilder extends BaseBuilder {
|
||||
* @param javaProject the javaProject object.
|
||||
* @param referencedJavaProjects the java projects that this project references.
|
||||
* @throws IOException
|
||||
* @throws CoreException
|
||||
*/
|
||||
private void writeStandardResources(SignedJarBuilder jarBuilder, IJavaProject javaProject,
|
||||
IJavaProject[] referencedJavaProjects) throws IOException {
|
||||
IJavaProject[] referencedJavaProjects) throws IOException, CoreException {
|
||||
IWorkspace ws = ResourcesPlugin.getWorkspace();
|
||||
IWorkspaceRoot wsRoot = ws.getRoot();
|
||||
|
||||
@@ -888,7 +979,9 @@ public class ApkBuilder extends BaseBuilder {
|
||||
writeStandardProjectResources(jarBuilder, javaProject, wsRoot, list);
|
||||
|
||||
for (IJavaProject referencedJavaProject : referencedJavaProjects) {
|
||||
writeStandardProjectResources(jarBuilder, referencedJavaProject, wsRoot, list);
|
||||
if (referencedJavaProject.getProject().hasNature(AndroidConstants.NATURE)) {
|
||||
writeStandardProjectResources(jarBuilder, referencedJavaProject, wsRoot, list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -977,7 +1070,9 @@ public class ApkBuilder extends BaseBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of the output folders for the specified {@link IJavaProject} objects.
|
||||
* Returns the list of the output folders for the specified {@link IJavaProject} objects, if
|
||||
* they are Android projects.
|
||||
*
|
||||
* @param referencedJavaProjects the java projects.
|
||||
* @return an array, always. Can be empty.
|
||||
* @throws CoreException
|
||||
@@ -989,19 +1084,21 @@ public class ApkBuilder extends BaseBuilder {
|
||||
IWorkspaceRoot wsRoot = ws.getRoot();
|
||||
|
||||
for (IJavaProject javaProject : referencedJavaProjects) {
|
||||
// get the output folder
|
||||
IPath path = null;
|
||||
try {
|
||||
path = javaProject.getOutputLocation();
|
||||
} catch (JavaModelException e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
IResource outputResource = wsRoot.findMember(path);
|
||||
if (outputResource != null && outputResource.getType() == IResource.FOLDER) {
|
||||
String outputOsPath = outputResource.getLocation().toOSString();
|
||||
|
||||
list.add(outputOsPath);
|
||||
if (javaProject.getProject().hasNature(AndroidConstants.NATURE)) {
|
||||
// get the output folder
|
||||
IPath path = null;
|
||||
try {
|
||||
path = javaProject.getOutputLocation();
|
||||
} catch (JavaModelException e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
IResource outputResource = wsRoot.findMember(path);
|
||||
if (outputResource != null && outputResource.getType() == IResource.FOLDER) {
|
||||
String outputOsPath = outputResource.getLocation().toOSString();
|
||||
|
||||
list.add(outputOsPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1026,7 +1123,7 @@ public class ApkBuilder extends BaseBuilder {
|
||||
|
||||
return list.toArray(new IJavaProject[list.size()]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks a {@link IFile} to make sure it should be packaged as standard resources.
|
||||
* @param file the IFile representing the file.
|
||||
|
||||
@@ -192,11 +192,16 @@ public class ApkDeltaVisitor extends BaseDeltaVisitor
|
||||
IPath parentPath = path.removeLastSegments(1);
|
||||
if (mOutputPath.equals(parentPath)) {
|
||||
String resourceName = resource.getName();
|
||||
// check if classes.dex was removed
|
||||
if (resourceName.equalsIgnoreCase(AndroidConstants.FN_CLASSES_DEX)) {
|
||||
mConvertToDex = true;
|
||||
mMakeFinalPackage = true;
|
||||
} else if (resourceName.equalsIgnoreCase(
|
||||
AndroidConstants.FN_RESOURCES_AP_)) {
|
||||
AndroidConstants.FN_RESOURCES_AP_) ||
|
||||
AndroidConstants.PATTERN_RESOURCES_S_AP_.matcher(
|
||||
resourceName).matches()) {
|
||||
// or if the default resources.ap_ or a configured version
|
||||
// (resources-###.ap_) was removed.
|
||||
mPackageResources = true;
|
||||
mMakeFinalPackage = true;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.ide.eclipse.adt.build;
|
||||
import com.android.ide.eclipse.adt.AdtConstants;
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.project.ProjectHelper;
|
||||
import com.android.ide.eclipse.adt.sdk.LoadStatus;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.common.project.BaseProjectHelper;
|
||||
import com.android.ide.eclipse.common.project.XmlErrorHandler;
|
||||
@@ -26,6 +27,7 @@ import com.android.ide.eclipse.common.project.XmlErrorHandler.XmlErrorListener;
|
||||
|
||||
import org.eclipse.core.resources.IContainer;
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.resources.IFolder;
|
||||
import org.eclipse.core.resources.IMarker;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
@@ -34,6 +36,10 @@ import org.eclipse.core.resources.IncrementalProjectBuilder;
|
||||
import org.eclipse.core.resources.ResourcesPlugin;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.eclipse.core.runtime.SubProgressMonitor;
|
||||
import org.eclipse.jdt.core.IClasspathEntry;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jdt.core.JavaCore;
|
||||
@@ -144,6 +150,15 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
|
||||
private final static Pattern sPattern8Line1 = Pattern.compile(
|
||||
"^(invalid resource directory name): (.*)$"); //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* 2 line aapt error<br>
|
||||
* "ERROR: Invalid configuration: foo"<br>
|
||||
* " ^^^"<br>
|
||||
* There's no need to parse the 2nd line.
|
||||
*/
|
||||
private final static Pattern sPattern9Line1 = Pattern.compile(
|
||||
"^Invalid configuration: (.+)$"); //$NON-NLS-1$
|
||||
|
||||
/** SAX Parser factory. */
|
||||
private SAXParserFactory mParserFactory;
|
||||
|
||||
@@ -435,8 +450,8 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
|
||||
String location = m.group(1);
|
||||
|
||||
// check the values and attempt to mark the file.
|
||||
if (checkAndMark(location, lineStr, msg, osRoot,
|
||||
project, IMarker.SEVERITY_ERROR) == false) {
|
||||
if (checkAndMark(location, lineStr, msg, osRoot, project,
|
||||
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
@@ -460,7 +475,7 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
|
||||
|
||||
// display the error
|
||||
if (checkAndMark(location, null, msg, osRoot, project,
|
||||
IMarker.SEVERITY_ERROR) == false) {
|
||||
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -483,8 +498,8 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
|
||||
String lineStr = m.group(2);
|
||||
|
||||
// check the values and attempt to mark the file.
|
||||
if (checkAndMark(location, lineStr, msg, osRoot,
|
||||
project, IMarker.SEVERITY_ERROR) == false) {
|
||||
if (checkAndMark(location, lineStr, msg, osRoot, project,
|
||||
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
@@ -497,8 +512,8 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
|
||||
String msg = m.group(3);
|
||||
|
||||
// check the values and attempt to mark the file.
|
||||
if (checkAndMark(location, lineStr, msg, osRoot,
|
||||
project, IMarker.SEVERITY_ERROR) == false) {
|
||||
if (checkAndMark(location, lineStr, msg, osRoot, project,
|
||||
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -521,8 +536,8 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
|
||||
String lineStr = m.group(2);
|
||||
|
||||
// check the values and attempt to mark the file.
|
||||
if (checkAndMark(location, lineStr, msg, osRoot,
|
||||
project, IMarker.SEVERITY_ERROR) == false) {
|
||||
if (checkAndMark(location, lineStr, msg, osRoot, project,
|
||||
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -537,8 +552,8 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
|
||||
String msg = m.group(3);
|
||||
|
||||
// check the values and attempt to mark the file.
|
||||
if (checkAndMark(location, lineStr, msg, osRoot,
|
||||
project,IMarker.SEVERITY_WARNING) == false) {
|
||||
if (checkAndMark(location, lineStr, msg, osRoot, project,
|
||||
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_WARNING) == false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -553,8 +568,8 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
|
||||
String msg = m.group(3);
|
||||
|
||||
// check the values and attempt to mark the file.
|
||||
if (checkAndMark(location, lineStr, msg, osRoot,
|
||||
project, IMarker.SEVERITY_ERROR) == false) {
|
||||
if (checkAndMark(location, lineStr, msg, osRoot, project,
|
||||
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -569,7 +584,25 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
|
||||
|
||||
// check the values and attempt to mark the file.
|
||||
if (checkAndMark(location, null, msg, osRoot, project,
|
||||
IMarker.SEVERITY_ERROR) == false) {
|
||||
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// success, go to the next line
|
||||
continue;
|
||||
}
|
||||
|
||||
m = sPattern9Line1.matcher(p);
|
||||
if (m.matches()) {
|
||||
String badConfig = m.group(1);
|
||||
String msg = String.format("APK Configuration filter '%1$s' is invalid", badConfig);
|
||||
|
||||
// skip the next line
|
||||
i++;
|
||||
|
||||
// check the values and attempt to mark the file.
|
||||
if (checkAndMark(null /*location*/, null, msg, osRoot, project,
|
||||
AndroidConstants.MARKER_AAPT_PACKAGE, IMarker.SEVERITY_ERROR) == false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -654,23 +687,25 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
|
||||
/**
|
||||
* Check if the parameters gotten from the error output are valid, and mark
|
||||
* the file with an AAPT marker.
|
||||
* @param location
|
||||
* @param location the full OS path of the error file. If null, the project is marked
|
||||
* @param lineStr
|
||||
* @param message
|
||||
* @param root The root directory of the project, in OS specific format.
|
||||
* @param project
|
||||
* @param markerId The marker id to put.
|
||||
* @param severity The severity of the marker to put (IMarker.SEVERITY_*)
|
||||
* @return true if the parameters were valid and the file was marked
|
||||
* sucessfully.
|
||||
* @return true if the parameters were valid and the file was marked successfully.
|
||||
*
|
||||
* @see IMarker
|
||||
*/
|
||||
private final boolean checkAndMark(String location, String lineStr,
|
||||
String message, String root, IProject project, int severity) {
|
||||
String message, String root, IProject project, String markerId, int severity) {
|
||||
// check this is in fact a file
|
||||
File f = new File(location);
|
||||
if (f.exists() == false) {
|
||||
return false;
|
||||
if (location != null) {
|
||||
File f = new File(location);
|
||||
if (f.exists() == false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// get the line number
|
||||
@@ -687,16 +722,18 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
|
||||
}
|
||||
|
||||
// add the marker
|
||||
IResource f2 = getResourceFromFullPath(location, root, project);
|
||||
if (f2 == null) {
|
||||
return false;
|
||||
IResource f2 = project;
|
||||
if (location != null) {
|
||||
f2 = getResourceFromFullPath(location, root, project);
|
||||
if (f2 == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// check if there's a similar marker already, since aapt is launched twice
|
||||
boolean markerAlreadyExists = false;
|
||||
try {
|
||||
IMarker[] markers = f2.findMarkers(AndroidConstants.MARKER_AAPT, true,
|
||||
IResource.DEPTH_ZERO);
|
||||
IMarker[] markers = f2.findMarkers(markerId, true, IResource.DEPTH_ZERO);
|
||||
|
||||
for (IMarker marker : markers) {
|
||||
int tmpLine = marker.getAttribute(IMarker.LINE_NUMBER, -1);
|
||||
@@ -727,10 +764,10 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
|
||||
|
||||
if (markerAlreadyExists == false) {
|
||||
if (line != -1) {
|
||||
BaseProjectHelper.addMarker(f2, AndroidConstants.MARKER_AAPT, message, line,
|
||||
BaseProjectHelper.addMarker(f2, markerId, message, line,
|
||||
severity);
|
||||
} else {
|
||||
BaseProjectHelper.addMarker(f2, AndroidConstants.MARKER_AAPT, message, severity);
|
||||
BaseProjectHelper.addMarker(f2, markerId, message, severity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -841,4 +878,64 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
|
||||
|
||||
return oslibraryList.toArray(new String[oslibraryList.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts the build if the SDK/project setups are broken. This does not
|
||||
* display any errors.
|
||||
*
|
||||
* @param project The {@link IJavaProject} being compiled.
|
||||
* @throws CoreException
|
||||
*/
|
||||
protected final void abortOnBadSetup(IProject project) throws CoreException {
|
||||
// check if we have finished loading the SDK.
|
||||
if (AdtPlugin.getDefault().getSdkLoadStatus() != LoadStatus.LOADED) {
|
||||
// we exit silently
|
||||
stopBuild("SDK is not loaded yet");
|
||||
}
|
||||
|
||||
// abort if there are TARGET or ADT type markers
|
||||
IMarker[] markers = project.findMarkers(AdtConstants.MARKER_TARGET,
|
||||
false /*includeSubtypes*/, IResource.DEPTH_ZERO);
|
||||
|
||||
if (markers.length > 0) {
|
||||
stopBuild("");
|
||||
}
|
||||
|
||||
markers = project.findMarkers(AdtConstants.MARKER_ADT, false /*includeSubtypes*/,
|
||||
IResource.DEPTH_ZERO);
|
||||
|
||||
if (markers.length > 0) {
|
||||
stopBuild("");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception to cancel the build.
|
||||
*
|
||||
* @param error the error message
|
||||
* @param args the printf-style arguments to the error message.
|
||||
* @throws CoreException
|
||||
*/
|
||||
protected final void stopBuild(String error, Object... args) throws CoreException {
|
||||
throw new CoreException(new Status(IStatus.CANCEL, AdtPlugin.PLUGIN_ID,
|
||||
String.format(error, args)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively delete all the derived resources.
|
||||
*/
|
||||
protected void removeDerivedResources(IResource resource, IProgressMonitor monitor)
|
||||
throws CoreException {
|
||||
if (resource.exists()) {
|
||||
if (resource.isDerived()) {
|
||||
resource.delete(true, new SubProgressMonitor(monitor, 10));
|
||||
} else if (resource.getType() == IResource.FOLDER) {
|
||||
IFolder folder = (IFolder)resource;
|
||||
IResource[] members = folder.members();
|
||||
for (IResource member : members) {
|
||||
removeDerivedResources(member, monitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
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;
|
||||
@@ -46,10 +45,6 @@ public final class DexWrapper {
|
||||
|
||||
private final static String MAIN_RUN = "run"; //$NON-NLS-1$
|
||||
|
||||
private static DexWrapper sWrapper;
|
||||
|
||||
private static LoadStatus sLoadStatus = LoadStatus.LOADING;
|
||||
|
||||
private Method mRunMethod;
|
||||
|
||||
private Constructor<?> mArgConstructor;
|
||||
@@ -62,15 +57,16 @@ public final class DexWrapper {
|
||||
private Field mConsoleErr;
|
||||
|
||||
/**
|
||||
* Loads the dex library from a file path. The loaded library can be used with the
|
||||
* {@link DexWrapper} object returned by {@link #getWrapper()}
|
||||
* Loads the dex library from a file path.
|
||||
*
|
||||
* The loaded library can be used via
|
||||
* {@link DexWrapper#run(String, String[], boolean, PrintStream, PrintStream)}.
|
||||
*
|
||||
* @param osFilepath the location of the dex.jar file.
|
||||
* @return an IStatus indicating the result of the load.
|
||||
*/
|
||||
public static synchronized IStatus loadDex(String osFilepath) {
|
||||
public synchronized IStatus loadDex(String osFilepath) {
|
||||
try {
|
||||
sWrapper = null;
|
||||
|
||||
File f = new File(osFilepath);
|
||||
if (f.isFile() == false) {
|
||||
return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, String.format(
|
||||
@@ -86,44 +82,38 @@ public final class DexWrapper {
|
||||
Class<?> consoleClass = loader.loadClass(DEX_CONSOLE);
|
||||
Class<?> argClass = loader.loadClass(DEX_ARGS);
|
||||
|
||||
sWrapper = new DexWrapper(mainClass, argClass, consoleClass);
|
||||
|
||||
try {
|
||||
// now get the fields/methods we need
|
||||
mRunMethod = mainClass.getMethod(MAIN_RUN, argClass);
|
||||
|
||||
mArgConstructor = argClass.getConstructor();
|
||||
mArgOutName = argClass.getField("outName"); //$NON-NLS-1$
|
||||
mArgJarOutput = argClass.getField("jarOutput"); //$NON-NLS-1$
|
||||
mArgFileNames = argClass.getField("fileNames"); //$NON-NLS-1$
|
||||
mArgVerbose = argClass.getField("verbose"); //$NON-NLS-1$
|
||||
|
||||
mConsoleOut = consoleClass.getField("out"); //$NON-NLS-1$
|
||||
mConsoleErr = consoleClass.getField("err"); //$NON-NLS-1$
|
||||
|
||||
} catch (SecurityException e) {
|
||||
return createErrorStatus(Messages.DexWrapper_SecuryEx_Unable_To_Find_API, e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
return createErrorStatus(Messages.DexWrapper_SecuryEx_Unable_To_Find_Method, e);
|
||||
} catch (NoSuchFieldException e) {
|
||||
return createErrorStatus(Messages.DexWrapper_SecuryEx_Unable_To_Find_Field, e);
|
||||
}
|
||||
|
||||
return Status.OK_STATUS;
|
||||
} catch (MalformedURLException e) {
|
||||
// really this should not happen.
|
||||
return createErrorStatus(String.format(Messages.DexWrapper_Failed_to_load_s, osFilepath), e);
|
||||
return createErrorStatus(
|
||||
String.format(Messages.DexWrapper_Failed_to_load_s, osFilepath), e);
|
||||
} catch (ClassNotFoundException e) {
|
||||
return createErrorStatus(String.format(Messages.DexWrapper_Failed_to_load_s, osFilepath), e);
|
||||
} catch (CoreException e) {
|
||||
return e.getStatus();
|
||||
} finally {
|
||||
if (sWrapper == null) {
|
||||
sLoadStatus = LoadStatus.FAILED;
|
||||
} else {
|
||||
sLoadStatus = LoadStatus.LOADED;
|
||||
}
|
||||
return createErrorStatus(
|
||||
String.format(Messages.DexWrapper_Failed_to_load_s, osFilepath), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unloads the loaded dex wrapper.
|
||||
*/
|
||||
public static synchronized void unloadDex() {
|
||||
sWrapper = null;
|
||||
sLoadStatus = LoadStatus.LOADING;
|
||||
}
|
||||
|
||||
public static synchronized DexWrapper getWrapper() {
|
||||
return sWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link LoadStatus}.
|
||||
*/
|
||||
public static synchronized LoadStatus getStatus() {
|
||||
return sLoadStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the dex command.
|
||||
* @param osOutFilePath the OS path to the outputfile (classes.dex
|
||||
@@ -169,33 +159,6 @@ public final class DexWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
private DexWrapper(Class<?> mainClass, Class<?> argClass, Class<?> consoleClass)
|
||||
throws CoreException {
|
||||
try {
|
||||
// now get the fields/methods we need
|
||||
mRunMethod = mainClass.getMethod(MAIN_RUN, argClass);
|
||||
|
||||
mArgConstructor = argClass.getConstructor();
|
||||
mArgOutName = argClass.getField("outName"); //$NON-NLS-1$
|
||||
mArgJarOutput = argClass.getField("jarOutput"); //$NON-NLS-1$
|
||||
mArgFileNames = argClass.getField("fileNames"); //$NON-NLS-1$
|
||||
mArgVerbose = argClass.getField("verbose"); //$NON-NLS-1$
|
||||
|
||||
mConsoleOut = consoleClass.getField("out"); //$NON-NLS-1$
|
||||
mConsoleErr = consoleClass.getField("err"); //$NON-NLS-1$
|
||||
|
||||
} catch (SecurityException e) {
|
||||
throw new CoreException(createErrorStatus(
|
||||
Messages.DexWrapper_SecuryEx_Unable_To_Find_API, e));
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new CoreException(createErrorStatus(
|
||||
Messages.DexWrapper_SecuryEx_Unable_To_Find_Method, e));
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new CoreException(createErrorStatus(
|
||||
Messages.DexWrapper_SecuryEx_Unable_To_Find_Field, e));
|
||||
}
|
||||
}
|
||||
|
||||
private static IStatus createErrorStatus(String message, Exception e) {
|
||||
AdtPlugin.log(e, message);
|
||||
AdtPlugin.printErrorToConsole(Messages.DexWrapper_Dex_Loader, message);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,7 @@ package com.android.ide.eclipse.adt.build;
|
||||
import com.android.ide.eclipse.adt.AdtConstants;
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.build.BaseBuilder.BaseDeltaVisitor;
|
||||
import com.android.ide.eclipse.adt.project.ProjectHelper;
|
||||
import com.android.ide.eclipse.adt.build.PreCompilerBuilder.AidlData;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.common.project.AndroidManifestParser;
|
||||
import com.android.ide.eclipse.common.project.BaseProjectHelper;
|
||||
@@ -31,14 +31,25 @@ import org.eclipse.core.resources.IFolder;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
import org.eclipse.core.resources.IResourceDelta;
|
||||
import org.eclipse.core.resources.IResourceDeltaVisitor;
|
||||
import org.eclipse.core.resources.IWorkspaceRoot;
|
||||
import org.eclipse.core.resources.ResourcesPlugin;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.core.runtime.Path;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Resource Delta visitor for the pre-compiler.
|
||||
* <p/>This delta visitor only cares about files that are the source or the result of actions of the
|
||||
* {@link PreCompilerBuilder}:
|
||||
* <ul><li>R.java/Manifest.java generated by compiling the resources</li>
|
||||
* <li>Any Java files generated by <code>aidl</code></li></ul>.
|
||||
*
|
||||
* Therefore it looks for the following:
|
||||
* <ul><li>Any modification in the resource folder</li>
|
||||
* <li>Removed files from the source folder receiving generated Java files</li>
|
||||
* <li>Any modification to aidl files.</li>
|
||||
*
|
||||
*/
|
||||
class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
|
||||
IResourceDeltaVisitor {
|
||||
@@ -53,14 +64,11 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
|
||||
private boolean mCompileResources = false;
|
||||
|
||||
/** List of .aidl files found that are modified or new. */
|
||||
private final ArrayList<IFile> mAidlToCompile = new ArrayList<IFile>();
|
||||
private final ArrayList<AidlData> mAidlToCompile = new ArrayList<AidlData>();
|
||||
|
||||
/** List of .aidl files that have been removed. */
|
||||
private final ArrayList<IFile> mAidlToRemove = new ArrayList<IFile>();
|
||||
private final ArrayList<AidlData> mAidlToRemove = new ArrayList<AidlData>();
|
||||
|
||||
/** Aidl forced recompilation flag. This is set to true if project.aidl is modified. */
|
||||
private boolean mFullAidlCompilation = false;
|
||||
|
||||
/** Manifest check/parsing flag. */
|
||||
private boolean mCheckedManifestXml = false;
|
||||
|
||||
@@ -75,36 +83,36 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
|
||||
private boolean mInRes = false;
|
||||
|
||||
/**
|
||||
* In Source folder flag. This allows us to know if we're in a source
|
||||
* folder.
|
||||
* Current Source folder. This allows us to know if we're in a source
|
||||
* folder, and which folder.
|
||||
*/
|
||||
private boolean mInSrc = false;
|
||||
private IFolder mSourceFolder = null;
|
||||
|
||||
/** List of source folders. */
|
||||
private ArrayList<IPath> mSourceFolders;
|
||||
private boolean mIsGenSourceFolder = false;
|
||||
|
||||
private IWorkspaceRoot mRoot;
|
||||
|
||||
|
||||
public PreCompilerDeltaVisitor(BaseBuilder builder, ArrayList<IPath> sourceFolders) {
|
||||
super(builder);
|
||||
mSourceFolders = sourceFolders;
|
||||
mRoot = ResourcesPlugin.getWorkspace().getRoot();
|
||||
}
|
||||
|
||||
public boolean getCompileResources() {
|
||||
return mCompileResources;
|
||||
}
|
||||
|
||||
public ArrayList<IFile> getAidlToCompile() {
|
||||
public ArrayList<AidlData> getAidlToCompile() {
|
||||
return mAidlToCompile;
|
||||
}
|
||||
|
||||
public ArrayList<IFile> getAidlToRemove() {
|
||||
public ArrayList<AidlData> getAidlToRemove() {
|
||||
return mAidlToRemove;
|
||||
}
|
||||
|
||||
public boolean getFullAidlRecompilation() {
|
||||
return mFullAidlCompilation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the manifest file was parsed/checked for error during the resource delta
|
||||
* visiting.
|
||||
@@ -149,11 +157,13 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
|
||||
// since the delta visitor also visits the root we return true if
|
||||
// segments.length = 1
|
||||
if (segments.length == 1) {
|
||||
// FIXME: check this is an Android project.
|
||||
return true;
|
||||
} else if (segments.length == 2) {
|
||||
// if we are at an item directly under the root directory,
|
||||
// then we are not yet in a source or resource folder
|
||||
mInRes = mInSrc = false;
|
||||
mInRes = false;
|
||||
mSourceFolder = null;
|
||||
|
||||
if (SdkConstants.FD_RESOURCES.equalsIgnoreCase(segments[1])) {
|
||||
// this is the resource folder that was modified. we want to
|
||||
@@ -162,7 +172,7 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
|
||||
// since we're going to visit its children next, we set the
|
||||
// flag
|
||||
mInRes = true;
|
||||
mInSrc = false;
|
||||
mSourceFolder = null;
|
||||
return true;
|
||||
} else if (AndroidConstants.FN_ANDROID_MANIFEST.equalsIgnoreCase(segments[1])) {
|
||||
// any change in the manifest could trigger a new R.java
|
||||
@@ -183,9 +193,6 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
|
||||
// we don't want to go to the children, not like they are
|
||||
// any for this resource anyway.
|
||||
return false;
|
||||
} else if (AndroidConstants.FN_PROJECT_AIDL.equalsIgnoreCase(segments[1])) {
|
||||
// need to force recompilation of all the aidl files
|
||||
mFullAidlCompilation = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +205,7 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
|
||||
// so first we test if we already know we are in a source or
|
||||
// resource folder.
|
||||
|
||||
if (mInSrc) {
|
||||
if (mSourceFolder != null) {
|
||||
// if we are in the res folder, we are looking for the following changes:
|
||||
// - added/removed/modified aidl files.
|
||||
// - missing R.java file
|
||||
@@ -216,127 +223,81 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
|
||||
// get the modification kind
|
||||
int kind = delta.getKind();
|
||||
|
||||
if (kind == IResourceDelta.ADDED) {
|
||||
// we only care about added files (inside the source folders), if they
|
||||
// are aidl files.
|
||||
// we process normal source folder and the 'gen' source folder differently.
|
||||
if (mIsGenSourceFolder) {
|
||||
// this is the generated java file source folder.
|
||||
// - if R.java/Manifest.java are removed/modified, we recompile the resources
|
||||
// - if aidl files are removed/modified, we recompile them.
|
||||
|
||||
// get the extension of the resource
|
||||
String ext = resource.getFileExtension();
|
||||
boolean outputWarning = false;
|
||||
|
||||
if (AndroidConstants.EXT_AIDL.equalsIgnoreCase(ext)) {
|
||||
// look for an already existing matching java file
|
||||
String javaName = resource.getName().replaceAll(
|
||||
AndroidConstants.RE_AIDL_EXT,
|
||||
AndroidConstants.DOT_JAVA);
|
||||
String fileName = resource.getName();
|
||||
|
||||
// get the parent container
|
||||
IContainer ic = resource.getParent();
|
||||
// Special case of R.java/Manifest.java.
|
||||
if (AndroidConstants.FN_RESOURCE_CLASS.equals(fileName) ||
|
||||
AndroidConstants.FN_MANIFEST_CLASS.equals(fileName)) {
|
||||
// if it was removed, there's a possibility that it was removed due to a
|
||||
// package change, or an aidl that was removed, but the only thing
|
||||
// that will happen is that we'll have an extra build. Not much of a problem.
|
||||
mCompileResources = true;
|
||||
|
||||
IFile javaFile = ic.getFile(new Path(javaName));
|
||||
if (javaFile != null && javaFile.exists()) {
|
||||
// check if that file was generated by the plugin. Normally those files are
|
||||
// deleted automatically, but it's better to check.
|
||||
String aidlPath = ProjectHelper.loadStringProperty(javaFile,
|
||||
PreCompilerBuilder.PROPERTY_ANDROID_GENERATED);
|
||||
if (aidlPath == null) {
|
||||
// mark the aidl file that it cannot be compile just yet
|
||||
ProjectHelper.saveBooleanProperty(file,
|
||||
PreCompilerBuilder.PROPERTY_ANDROID_CONFLICT, true);
|
||||
}
|
||||
|
||||
// we add it anyway so that we can try to compile it at every compilation
|
||||
// until the conflict is fixed.
|
||||
mAidlToCompile.add(file);
|
||||
|
||||
} else {
|
||||
// the java file doesn't exist, we can safely add the file to the list
|
||||
// of files to compile.
|
||||
mAidlToCompile.add(file);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// get the filename
|
||||
String fileName = segments[segments.length - 1];
|
||||
|
||||
boolean outputMessage = false;
|
||||
|
||||
// Special case of R.java/Manifest.java.
|
||||
// FIXME: This does not check the package. Any modification of R.java/Manifest.java in another project will trigger a new recompilation of the resources.
|
||||
if (AndroidConstants.FN_RESOURCE_CLASS.equals(fileName) ||
|
||||
AndroidConstants.FN_MANIFEST_CLASS.equals(fileName)) {
|
||||
// if it was removed, there's a possibility that it was removed due to a
|
||||
// package change, or an aidl that was removed, but the only thing
|
||||
// that will happen is that we'll have an extra build. Not much of a problem.
|
||||
mCompileResources = true;
|
||||
|
||||
// we want a warning
|
||||
outputMessage = true;
|
||||
} else {
|
||||
|
||||
// get the extension of the resource
|
||||
String ext = resource.getFileExtension();
|
||||
|
||||
if (AndroidConstants.EXT_AIDL.equalsIgnoreCase(ext)) {
|
||||
if (kind == IResourceDelta.REMOVED) {
|
||||
mAidlToRemove.add(file);
|
||||
} else {
|
||||
mAidlToCompile.add(file);
|
||||
}
|
||||
// we want a warning
|
||||
outputWarning = true;
|
||||
} else {
|
||||
if (kind == IResourceDelta.REMOVED) {
|
||||
// the file has been removed. we need to check it's a java file and that
|
||||
// there's a matching aidl file. We can't check its persistent storage
|
||||
// anymore.
|
||||
if (AndroidConstants.EXT_JAVA.equalsIgnoreCase(ext)) {
|
||||
String aidlFile = resource.getName().replaceAll(
|
||||
AndroidConstants.RE_JAVA_EXT,
|
||||
AndroidConstants.DOT_AIDL);
|
||||
|
||||
// get the parent container
|
||||
IContainer ic = resource.getParent();
|
||||
|
||||
IFile f = ic.getFile(new Path(aidlFile));
|
||||
if (f != null && f.exists() ) {
|
||||
// make sure that the aidl file is not in conflict anymore, in
|
||||
// case the java file was not generated by us.
|
||||
if (ProjectHelper.loadBooleanProperty(f,
|
||||
PreCompilerBuilder.PROPERTY_ANDROID_CONFLICT, false)) {
|
||||
ProjectHelper.saveBooleanProperty(f,
|
||||
PreCompilerBuilder.PROPERTY_ANDROID_CONFLICT, false);
|
||||
} else {
|
||||
outputMessage = true;
|
||||
}
|
||||
mAidlToCompile.add(f);
|
||||
// this has to be a Java file created from an aidl file.
|
||||
// Look for the source aidl file in all the source folders.
|
||||
String aidlFileName = fileName.replaceAll(AndroidConstants.RE_JAVA_EXT,
|
||||
AndroidConstants.DOT_AIDL);
|
||||
|
||||
for (IPath sourceFolderPath : mSourceFolders) {
|
||||
// do not search in the current source folder as it is the 'gen' folder.
|
||||
if (sourceFolderPath.equals(mSourceFolder.getFullPath())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
IFolder sourceFolder = getFolder(sourceFolderPath);
|
||||
if (sourceFolder != null) {
|
||||
// go recursively, segment by segment.
|
||||
// index starts at 2 (0 is project, 1 is 'gen'
|
||||
IFile sourceFile = findFile(sourceFolder, segments, 2, aidlFileName);
|
||||
|
||||
if (sourceFile != null) {
|
||||
// found the source. add it to the list of files to compile
|
||||
mAidlToCompile.add(new AidlData(sourceFolder, sourceFile));
|
||||
outputWarning = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// check if it's an android generated java file.
|
||||
IResource aidlSource = ProjectHelper.loadResourceProperty(
|
||||
file, PreCompilerBuilder.PROPERTY_ANDROID_GENERATED);
|
||||
|
||||
if (aidlSource != null && aidlSource.exists() &&
|
||||
aidlSource.getType() == IResource.FILE) {
|
||||
// it looks like this was a java file created from an aidl file.
|
||||
// we need to add the aidl file to the list of aidl file to compile
|
||||
mAidlToCompile.add((IFile)aidlSource);
|
||||
outputMessage = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (outputMessage) {
|
||||
if (kind == IResourceDelta.REMOVED) {
|
||||
// We pring an error just so that it's red, but it's just a warning really.
|
||||
String msg = String.format(Messages.s_Removed_Recreating_s, fileName);
|
||||
AdtPlugin.printErrorToConsole(mBuilder.getProject(), msg);
|
||||
} else if (kind == IResourceDelta.CHANGED) {
|
||||
// the file was modified manually! we can't allow it.
|
||||
String msg = String.format(Messages.s_Modified_Manually_Recreating_s, fileName);
|
||||
AdtPlugin.printErrorToConsole(mBuilder.getProject(), msg);
|
||||
if (outputWarning) {
|
||||
if (kind == IResourceDelta.REMOVED) {
|
||||
// We pring an error just so that it's red, but it's just a warning really.
|
||||
String msg = String.format(Messages.s_Removed_Recreating_s, fileName);
|
||||
AdtPlugin.printErrorToConsole(mBuilder.getProject(), msg);
|
||||
} else if (kind == IResourceDelta.CHANGED) {
|
||||
// the file was modified manually! we can't allow it.
|
||||
String msg = String.format(Messages.s_Modified_Manually_Recreating_s,
|
||||
fileName);
|
||||
AdtPlugin.printErrorToConsole(mBuilder.getProject(), msg);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// this is another source folder.
|
||||
// We only care about aidl files being added/modified/removed.
|
||||
|
||||
// get the extension of the resource
|
||||
String ext = resource.getFileExtension();
|
||||
if (AndroidConstants.EXT_AIDL.equalsIgnoreCase(ext)) {
|
||||
if (kind == IResourceDelta.REMOVED) {
|
||||
// we'll have to remove the generated file.
|
||||
mAidlToRemove.add(new AidlData(mSourceFolder, file));
|
||||
} else {
|
||||
// add the aidl file to the list of file to (re)compile
|
||||
mAidlToCompile.add(new AidlData(mSourceFolder, file));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,19 +364,25 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
|
||||
}
|
||||
} else if (resource instanceof IFolder) {
|
||||
// in this case we may be inside a folder that contains a source
|
||||
// folder.
|
||||
String[] sourceFolderSegments = findMatchingSourceFolder(mSourceFolders, segments);
|
||||
if (sourceFolderSegments != null) {
|
||||
// we have a match!
|
||||
mInRes = false;
|
||||
// folder, go through the list of known source folders
|
||||
|
||||
// Check if the current folder is actually a source folder
|
||||
if (sourceFolderSegments.length == segments.length) {
|
||||
mInSrc = true;
|
||||
for (IPath sourceFolderPath : mSourceFolders) {
|
||||
// first check if they match exactly.
|
||||
if (sourceFolderPath.equals(path)) {
|
||||
// this is a source folder!
|
||||
mInRes = false;
|
||||
mSourceFolder = getFolder(sourceFolderPath); // all non null due to test above
|
||||
mIsGenSourceFolder = path.segmentCount() == 2 &&
|
||||
path.segment(1).equals(SdkConstants.FD_GEN_SOURCES);
|
||||
return true;
|
||||
}
|
||||
|
||||
// and return true to visit the content, no matter what
|
||||
return true;
|
||||
// check if we are on the way to a source folder.
|
||||
int count = sourceFolderPath.matchingFirstSegments(path);
|
||||
if (count == path.segmentCount()) {
|
||||
mInRes = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// if we're here, we are visiting another folder
|
||||
@@ -429,4 +396,46 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for and return a file in a folder. The file is defined by its segments, and a new
|
||||
* name (replacing the last segment).
|
||||
* @param folder the folder we are searching
|
||||
* @param segments the segments of the file to search.
|
||||
* @param index the index of the current segment we are looking for
|
||||
* @param filename the new name to replace the last segment.
|
||||
* @return the {@link IFile} representing the searched file, or null if not found
|
||||
*/
|
||||
private IFile findFile(IFolder folder, String[] segments, int index, String filename) {
|
||||
boolean lastSegment = index == segments.length - 1;
|
||||
IResource resource = folder.findMember(lastSegment ? filename : segments[index]);
|
||||
if (resource != null && resource.exists()) {
|
||||
if (lastSegment) {
|
||||
if (resource.getType() == IResource.FILE) {
|
||||
return (IFile)resource;
|
||||
}
|
||||
} else {
|
||||
if (resource.getType() == IResource.FOLDER) {
|
||||
return findFile((IFolder)resource, segments, index+1, filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a handle to the folder identified by the given path in this container.
|
||||
* <p/>The different with {@link IContainer#getFolder(IPath)} is that this returns a non
|
||||
* null object only if the resource actually exists and is a folder (and not a file)
|
||||
* @param path the path of the folder to return.
|
||||
* @return a handle to the folder if it exists, or null otherwise.
|
||||
*/
|
||||
private IFolder getFolder(IPath path) {
|
||||
IResource resource = mRoot.findMember(path);
|
||||
if (resource != null && resource.exists() && resource.getType() == IResource.FOLDER) {
|
||||
return (IFolder)resource;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,24 +19,34 @@ 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.Sdk;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.common.project.BaseProjectHelper;
|
||||
import com.android.sdklib.IAndroidTarget;
|
||||
import com.android.sdklib.SdkConstants;
|
||||
|
||||
import org.eclipse.core.resources.IFolder;
|
||||
import org.eclipse.core.resources.IMarker;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
import org.eclipse.core.resources.IncrementalProjectBuilder;
|
||||
import org.eclipse.core.resources.IWorkspaceRoot;
|
||||
import org.eclipse.core.resources.ResourcesPlugin;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.core.runtime.SubProgressMonitor;
|
||||
import org.eclipse.jdt.core.IClasspathEntry;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jdt.core.JavaCore;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Resource manager builder whose only purpose is to refresh the resource folder
|
||||
* so that the other builder use an up to date version.
|
||||
*/
|
||||
public class ResourceManagerBuilder extends IncrementalProjectBuilder {
|
||||
public class ResourceManagerBuilder extends BaseBuilder {
|
||||
|
||||
public static final String ID = "com.android.ide.eclipse.adt.ResourceManagerBuilder"; //$NON-NLS-1$
|
||||
|
||||
@@ -54,6 +64,10 @@ public class ResourceManagerBuilder extends IncrementalProjectBuilder {
|
||||
|
||||
// Clear the project of the generic markers
|
||||
BaseBuilder.removeMarkersFromProject(project, AdtConstants.MARKER_ADT);
|
||||
|
||||
// check for existing target marker, in which case we abort.
|
||||
// (this means: no SDK, no target, or unresolvable target.)
|
||||
abortOnBadSetup(project);
|
||||
|
||||
// Check the compiler compliance level, displaying the error message
|
||||
// since this is the first builder.
|
||||
@@ -72,6 +86,109 @@ public class ResourceManagerBuilder extends IncrementalProjectBuilder {
|
||||
BaseProjectHelper.addMarker(project, AdtConstants.MARKER_ADT, errorMessage,
|
||||
IMarker.SEVERITY_ERROR);
|
||||
AdtPlugin.printErrorToConsole(project, errorMessage);
|
||||
|
||||
// interrupt the build. The next builders will not run.
|
||||
stopBuild(errorMessage);
|
||||
}
|
||||
|
||||
// Check that the SDK directory has been setup.
|
||||
String osSdkFolder = AdtPlugin.getOsSdkFolder();
|
||||
|
||||
if (osSdkFolder == null || osSdkFolder.length() == 0) {
|
||||
AdtPlugin.printErrorToConsole(project, Messages.No_SDK_Setup_Error);
|
||||
markProject(AdtConstants.MARKER_ADT, Messages.No_SDK_Setup_Error,
|
||||
IMarker.SEVERITY_ERROR);
|
||||
|
||||
// This interrupts the build. The next builders will not run.
|
||||
stopBuild(Messages.No_SDK_Setup_Error);
|
||||
}
|
||||
|
||||
// check the project has a target
|
||||
IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project);
|
||||
if (projectTarget == null) {
|
||||
// no target. marker has been set by the container initializer: exit silently.
|
||||
// This interrupts the build. The next builders will not run.
|
||||
stopBuild("Project has no target");
|
||||
}
|
||||
|
||||
// check the 'gen' source folder is present
|
||||
boolean hasGenSrcFolder = false; // whether the project has a 'gen' source folder setup
|
||||
IJavaProject javaProject = JavaCore.create(project);
|
||||
|
||||
IClasspathEntry[] classpaths = javaProject.readRawClasspath();
|
||||
if (classpaths != null) {
|
||||
for (IClasspathEntry e : classpaths) {
|
||||
if (e.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
|
||||
IPath path = e.getPath();
|
||||
if (path.segmentCount() == 2 &&
|
||||
path.segment(1).equals(SdkConstants.FD_GEN_SOURCES)) {
|
||||
hasGenSrcFolder = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean genFolderPresent = false; // whether the gen folder actually exists
|
||||
IResource resource = project.findMember(SdkConstants.FD_GEN_SOURCES);
|
||||
genFolderPresent = resource != null && resource.exists();
|
||||
|
||||
if (hasGenSrcFolder == false && genFolderPresent) {
|
||||
// No source folder setup for 'gen' in the project, but there's already a
|
||||
// 'gen' resource (file or folder).
|
||||
String message;
|
||||
if (resource.getType() == IResource.FOLDER) {
|
||||
// folder exists already! This is an error. If the folder had been created
|
||||
// by the NewProjectWizard, it'd be a source folder.
|
||||
message = String.format("%1$s already exists but is not a source folder. Convert to a source folder or rename it.",
|
||||
resource.getFullPath().toString());
|
||||
} else {
|
||||
// resource exists but is not a folder.
|
||||
message = String.format(
|
||||
"Resource %1$s is in the way. ADT needs a source folder called 'gen' to work. Rename or delete resource.",
|
||||
resource.getFullPath().toString());
|
||||
}
|
||||
|
||||
AdtPlugin.printErrorToConsole(project, message);
|
||||
markProject(AdtConstants.MARKER_ADT, message, IMarker.SEVERITY_ERROR);
|
||||
|
||||
// This interrupts the build. The next builders will not run.
|
||||
stopBuild(message);
|
||||
} else if (hasGenSrcFolder == false || genFolderPresent == false) {
|
||||
// either there is no 'gen' source folder in the project (older SDK),
|
||||
// or the folder does not exist (was deleted, or was a fresh svn checkout maybe.)
|
||||
|
||||
// In case we are migrating from an older SDK, we go through the current source
|
||||
// folders and delete the generated Java files.
|
||||
ArrayList<IPath> sourceFolders = BaseProjectHelper.getSourceClasspaths(javaProject);
|
||||
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
|
||||
for (IPath path : sourceFolders) {
|
||||
IResource member = root.findMember(path);
|
||||
if (member != null) {
|
||||
removeDerivedResources(member, monitor);
|
||||
}
|
||||
}
|
||||
|
||||
// create the new source folder, if needed
|
||||
IFolder genFolder = project.getFolder(SdkConstants.FD_GEN_SOURCES);
|
||||
if (genFolderPresent == false) {
|
||||
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
|
||||
"Creating 'gen' source folder for generated Java files");
|
||||
genFolder.create(true /* force */, true /* local */,
|
||||
new SubProgressMonitor(monitor, 10));
|
||||
genFolder.setDerived(true);
|
||||
}
|
||||
|
||||
// add it to the source folder list, if needed only (or it will throw)
|
||||
if (hasGenSrcFolder == false) {
|
||||
IClasspathEntry[] entries = javaProject.getRawClasspath();
|
||||
entries = ProjectHelper.addEntryToClasspath(entries,
|
||||
JavaCore.newSourceEntry(genFolder.getFullPath()));
|
||||
javaProject.setRawClasspath(entries, new SubProgressMonitor(monitor, 10));
|
||||
}
|
||||
|
||||
// refresh the whole project
|
||||
project.refreshLocal(IResource.DEPTH_INFINITE, new SubProgressMonitor(monitor, 10));
|
||||
}
|
||||
|
||||
// Check the preference to be sure we are supposed to refresh
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.ide.eclipse.adt.launch;
|
||||
|
||||
import com.android.ddmlib.IDevice;
|
||||
import com.android.ddmlib.MultiLineReceiver;
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
/**
|
||||
* Output receiver for am process (Activity Manager)
|
||||
*
|
||||
* Monitors adb output for am errors, and retries launch as appropriate.
|
||||
*/
|
||||
public class AMReceiver extends MultiLineReceiver {
|
||||
|
||||
private static final int MAX_ATTEMPT_COUNT = 5;
|
||||
private static final Pattern sAmErrorType = Pattern.compile("Error type (\\d+)"); //$NON-NLS-1$
|
||||
|
||||
private final DelayedLaunchInfo mLaunchInfo;
|
||||
private final IDevice mDevice;
|
||||
private final ILaunchController mLaunchController;
|
||||
|
||||
/**
|
||||
* Basic constructor.
|
||||
*
|
||||
* @param launchInfo the {@link DelayedLaunchInfo} associated with the am process.
|
||||
* @param device the Android device on which the launch is done.
|
||||
* @param launchController the {@link ILaunchController} that is managing the launch
|
||||
*/
|
||||
public AMReceiver(DelayedLaunchInfo launchInfo, IDevice device,
|
||||
ILaunchController launchController) {
|
||||
mLaunchInfo = launchInfo;
|
||||
mDevice = device;
|
||||
mLaunchController = launchController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitors the am process for error messages. If an error occurs, will reattempt launch up to
|
||||
* <code>MAX_ATTEMPT_COUNT</code> times.
|
||||
*
|
||||
* @param lines a portion of the am output
|
||||
*
|
||||
* @see MultiLineReceiver#processNewLines(String[])
|
||||
*/
|
||||
@Override
|
||||
public void processNewLines(String[] lines) {
|
||||
// first we check if one starts with error
|
||||
ArrayList<String> array = new ArrayList<String>();
|
||||
boolean error = false;
|
||||
boolean warning = false;
|
||||
for (String s : lines) {
|
||||
// ignore empty lines.
|
||||
if (s.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check for errors that output an error type, if the attempt count is still
|
||||
// valid. If not the whole text will be output in the console
|
||||
if (mLaunchInfo.getAttemptCount() < MAX_ATTEMPT_COUNT &&
|
||||
mLaunchInfo.isCancelled() == false) {
|
||||
Matcher m = sAmErrorType.matcher(s);
|
||||
if (m.matches()) {
|
||||
// get the error type
|
||||
int type = Integer.parseInt(m.group(1));
|
||||
|
||||
final int waitTime = 3;
|
||||
String msg;
|
||||
|
||||
switch (type) {
|
||||
case 1:
|
||||
/* Intended fall through */
|
||||
case 2:
|
||||
msg = String.format(
|
||||
"Device not ready. Waiting %1$d seconds before next attempt.",
|
||||
waitTime);
|
||||
break;
|
||||
case 3:
|
||||
msg = String.format(
|
||||
"New package not yet registered with the system. Waiting %1$d seconds before next attempt.",
|
||||
waitTime);
|
||||
break;
|
||||
default:
|
||||
msg = String.format(
|
||||
"Device not ready (%2$d). Waiting %1$d seconds before next attempt.",
|
||||
waitTime, type);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
AdtPlugin.printToConsole(mLaunchInfo.getProject(), msg);
|
||||
|
||||
// launch another thread, that waits a bit and attempts another launch
|
||||
new Thread("Delayed Launch attempt") {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
sleep(waitTime * 1000);
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
mLaunchController.launchApp(mLaunchInfo, mDevice);
|
||||
}
|
||||
}.start();
|
||||
|
||||
// no need to parse the rest
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// check for error if needed
|
||||
if (error == false && s.startsWith("Error:")) { //$NON-NLS-1$
|
||||
error = true;
|
||||
}
|
||||
if (warning == false && s.startsWith("Warning:")) { //$NON-NLS-1$
|
||||
warning = true;
|
||||
}
|
||||
|
||||
// add the line to the list
|
||||
array.add("ActivityManager: " + s); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
// then we display them in the console
|
||||
if (warning || error) {
|
||||
AdtPlugin.printErrorToConsole(mLaunchInfo.getProject(), array.toArray());
|
||||
} else {
|
||||
AdtPlugin.printToConsole(mLaunchInfo.getProject(), array.toArray());
|
||||
}
|
||||
|
||||
// if error then we cancel the launch, and remove the delayed info
|
||||
if (error) {
|
||||
mLaunchController.stopLaunch(mLaunchInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if launch has been cancelled
|
||||
*/
|
||||
public boolean isCancelled() {
|
||||
return mLaunchInfo.isCancelled();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.launch;
|
||||
|
||||
import com.android.ddmlib.IDevice;
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Launches the given activity
|
||||
*/
|
||||
public class ActivityLaunchAction implements IAndroidLaunchAction {
|
||||
|
||||
private final String mActivity;
|
||||
private final ILaunchController mLaunchController;
|
||||
|
||||
/**
|
||||
* Creates a ActivityLaunchAction
|
||||
*
|
||||
* @param activity fully qualified activity name to launch
|
||||
* @param controller the {@link ILaunchController} that performs launch
|
||||
*/
|
||||
public ActivityLaunchAction(String activity, ILaunchController controller) {
|
||||
mActivity = activity;
|
||||
mLaunchController = controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the activity on targeted device
|
||||
*
|
||||
* @param info the {@link DelayedLaunchInfo} that contains launch details
|
||||
* @param device the Android device to perform action on
|
||||
*
|
||||
* @see IAndroidLaunchAction#doLaunchAction(DelayedLaunchInfo, IDevice)
|
||||
*/
|
||||
public boolean doLaunchAction(DelayedLaunchInfo info, IDevice device) {
|
||||
try {
|
||||
String msg = String.format("Starting activity %1$s on device ", mActivity,
|
||||
device);
|
||||
AdtPlugin.printToConsole(info.getProject(), msg);
|
||||
|
||||
// In debug mode, we need to add the info to the list of application monitoring
|
||||
// client changes.
|
||||
// increment launch attempt count, to handle retries and timeouts
|
||||
info.incrementAttemptCount();
|
||||
|
||||
// now we actually launch the app.
|
||||
device.executeShellCommand("am start" //$NON-NLS-1$
|
||||
+ (info.isDebugMode() ? " -D" //$NON-NLS-1$
|
||||
: "") //$NON-NLS-1$
|
||||
+ " -n " //$NON-NLS-1$
|
||||
+ info.getPackageName() + "/" //$NON-NLS-1$
|
||||
+ mActivity.replaceAll("\\$", "\\\\\\$"), //$NON-NLS-1$ //$NON-NLS-2$
|
||||
new AMReceiver(info, device, mLaunchController));
|
||||
|
||||
// if the app is not a debug app, we need to do some clean up, as
|
||||
// the process is done!
|
||||
if (info.isDebugMode() == false) {
|
||||
// stop the launch object, since there's no debug, and it can't
|
||||
// provide any control over the app
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// something went wrong trying to launch the app.
|
||||
// lets stop the Launch
|
||||
AdtPlugin.printErrorToConsole(info.getProject(),
|
||||
String.format("Launch error: %s", e.getMessage()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a description of the activity being launched
|
||||
*
|
||||
* @see IAndroidLaunchAction#getLaunchDescription()
|
||||
*/
|
||||
public String getLaunchDescription() {
|
||||
return String.format("%1$s activity launch", mActivity);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.debug.launching;
|
||||
package com.android.ide.eclipse.adt.launch;
|
||||
|
||||
import org.eclipse.debug.core.DebugException;
|
||||
import org.eclipse.debug.core.ILaunchConfiguration;
|
||||
@@ -26,7 +26,7 @@ import org.eclipse.debug.core.model.ISourceLocator;
|
||||
* Custom implementation of Launch to allow access to the LaunchManager
|
||||
*
|
||||
*/
|
||||
class AndroidLaunch extends Launch {
|
||||
public class AndroidLaunch extends Launch {
|
||||
|
||||
/**
|
||||
* Basic constructor does nothing special
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.launch;
|
||||
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.debug.core.ILaunchConfiguration;
|
||||
|
||||
/**
|
||||
* Launch configuration data. This stores the result of querying the
|
||||
* {@link ILaunchConfiguration} so that it's only done once.
|
||||
*/
|
||||
public class AndroidLaunchConfiguration {
|
||||
|
||||
/**
|
||||
* Launch action. See {@link LaunchConfigDelegate#ACTION_DEFAULT},
|
||||
* {@link LaunchConfigDelegate#ACTION_ACTIVITY},
|
||||
* {@link LaunchConfigDelegate#ACTION_DO_NOTHING}
|
||||
*/
|
||||
public int mLaunchAction = LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION;
|
||||
|
||||
public static final boolean AUTO_TARGET_MODE = true;
|
||||
|
||||
/**
|
||||
* Target selection mode.
|
||||
* <ul>
|
||||
* <li><code>true</code>: automatic mode, see {@link #AUTO_TARGET_MODE}</li>
|
||||
* <li><code>false</code>: manual mode</li>
|
||||
* </ul>
|
||||
*/
|
||||
public boolean mTargetMode = LaunchConfigDelegate.DEFAULT_TARGET_MODE;
|
||||
|
||||
/**
|
||||
* Indicates whether the emulator should be called with -wipe-data
|
||||
*/
|
||||
public boolean mWipeData = LaunchConfigDelegate.DEFAULT_WIPE_DATA;
|
||||
|
||||
/**
|
||||
* Indicates whether the emulator should be called with -no-boot-anim
|
||||
*/
|
||||
public boolean mNoBootAnim = LaunchConfigDelegate.DEFAULT_NO_BOOT_ANIM;
|
||||
|
||||
/**
|
||||
* AVD Name.
|
||||
*/
|
||||
public String mAvdName = null;
|
||||
|
||||
public String mNetworkSpeed = EmulatorConfigTab.getSpeed(
|
||||
LaunchConfigDelegate.DEFAULT_SPEED);
|
||||
public String mNetworkDelay = EmulatorConfigTab.getDelay(
|
||||
LaunchConfigDelegate.DEFAULT_DELAY);
|
||||
|
||||
/**
|
||||
* Optional custom command line parameter to launch the emulator
|
||||
*/
|
||||
public String mEmulatorCommandLine;
|
||||
|
||||
/**
|
||||
* Initialized the structure from an ILaunchConfiguration object.
|
||||
* @param config
|
||||
*/
|
||||
public void set(ILaunchConfiguration config) {
|
||||
try {
|
||||
mLaunchAction = config.getAttribute(LaunchConfigDelegate.ATTR_LAUNCH_ACTION,
|
||||
mLaunchAction);
|
||||
} catch (CoreException e1) {
|
||||
// nothing to be done here, we'll use the default value
|
||||
}
|
||||
|
||||
try {
|
||||
mTargetMode = config.getAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE,
|
||||
mTargetMode);
|
||||
} catch (CoreException e) {
|
||||
// nothing to be done here, we'll use the default value
|
||||
}
|
||||
|
||||
try {
|
||||
mAvdName = config.getAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, mAvdName);
|
||||
} catch (CoreException e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
int index = LaunchConfigDelegate.DEFAULT_SPEED;
|
||||
try {
|
||||
index = config.getAttribute(LaunchConfigDelegate.ATTR_SPEED, index);
|
||||
} catch (CoreException e) {
|
||||
// nothing to be done here, we'll use the default value
|
||||
}
|
||||
mNetworkSpeed = EmulatorConfigTab.getSpeed(index);
|
||||
|
||||
index = LaunchConfigDelegate.DEFAULT_DELAY;
|
||||
try {
|
||||
index = config.getAttribute(LaunchConfigDelegate.ATTR_DELAY, index);
|
||||
} catch (CoreException e) {
|
||||
// nothing to be done here, we'll use the default value
|
||||
}
|
||||
mNetworkDelay = EmulatorConfigTab.getDelay(index);
|
||||
|
||||
try {
|
||||
mEmulatorCommandLine = config.getAttribute(
|
||||
LaunchConfigDelegate.ATTR_COMMANDLINE, ""); //$NON-NLS-1$
|
||||
} catch (CoreException e) {
|
||||
// lets not do anything here, we'll use the default value
|
||||
}
|
||||
|
||||
try {
|
||||
mWipeData = config.getAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA, mWipeData);
|
||||
} catch (CoreException e) {
|
||||
// nothing to be done here, we'll use the default value
|
||||
}
|
||||
|
||||
try {
|
||||
mNoBootAnim = config.getAttribute(LaunchConfigDelegate.ATTR_NO_BOOT_ANIM,
|
||||
mNoBootAnim);
|
||||
} catch (CoreException e) {
|
||||
// nothing to be done here, we'll use the default value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.launch;
|
||||
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
|
||||
import com.android.ddmlib.IDevice;
|
||||
|
||||
/**
|
||||
* A delayed launch waiting for a device to be present or ready before the
|
||||
* application is launched.
|
||||
*/
|
||||
public final class DelayedLaunchInfo {
|
||||
|
||||
/**
|
||||
* Used to indicate behavior when Android app already exists
|
||||
*/
|
||||
enum InstallRetryMode {
|
||||
NEVER, ALWAYS, PROMPT;
|
||||
}
|
||||
|
||||
/** The device on which to launch the app */
|
||||
private IDevice mDevice = null;
|
||||
|
||||
/** The eclipse project */
|
||||
private final IProject mProject;
|
||||
|
||||
/** Package name */
|
||||
private final String mPackageName;
|
||||
|
||||
/** IFile to the package (.apk) file */
|
||||
private final IFile mPackageFile;
|
||||
|
||||
/** debuggable attribute of the manifest file. */
|
||||
private final Boolean mDebuggable;
|
||||
|
||||
/** Required ApiVersionNumber by the app. 0 means no requirements */
|
||||
private final int mRequiredApiVersionNumber;
|
||||
|
||||
private InstallRetryMode mRetryMode = InstallRetryMode.NEVER;
|
||||
|
||||
/** Launch action. */
|
||||
private final IAndroidLaunchAction mLaunchAction;
|
||||
|
||||
/** the launch object */
|
||||
private final AndroidLaunch mLaunch;
|
||||
|
||||
/** the monitor object */
|
||||
private final IProgressMonitor mMonitor;
|
||||
|
||||
/** debug mode flag */
|
||||
private boolean mDebugMode;
|
||||
|
||||
/** current number of launch attempts */
|
||||
private int mAttemptCount = 0;
|
||||
|
||||
/** cancellation state of launch */
|
||||
private boolean mCancelled = false;
|
||||
|
||||
/**
|
||||
* Basic constructor with activity and package info.
|
||||
*
|
||||
* @param project the eclipse project that corresponds to Android app
|
||||
* @param packageName package name of Android app
|
||||
* @param launchAction action to perform after app install
|
||||
* @param pack IFile to the package (.apk) file
|
||||
* @param debuggable debuggable attribute of the app's manifest file.
|
||||
* @param requiredApiVersionNumber required SDK version by the app. 0 means no requirements.
|
||||
* @param launch the launch object
|
||||
* @param monitor progress monitor for launch
|
||||
*/
|
||||
public DelayedLaunchInfo(IProject project, String packageName,
|
||||
IAndroidLaunchAction launchAction, IFile pack, Boolean debuggable,
|
||||
int requiredApiVersionNumber, AndroidLaunch launch, IProgressMonitor monitor) {
|
||||
mProject = project;
|
||||
mPackageName = packageName;
|
||||
mPackageFile = pack;
|
||||
mLaunchAction = launchAction;
|
||||
mLaunch = launch;
|
||||
mMonitor = monitor;
|
||||
mDebuggable = debuggable;
|
||||
mRequiredApiVersionNumber = requiredApiVersionNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the device on which to launch the app
|
||||
*/
|
||||
public IDevice getDevice() {
|
||||
return mDevice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the device on which to launch the app
|
||||
*/
|
||||
public void setDevice(IDevice device) {
|
||||
mDevice = device;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the eclipse project that corresponds to Android app
|
||||
*/
|
||||
public IProject getProject() {
|
||||
return mProject;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the package name of the Android app
|
||||
*/
|
||||
public String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the application package file
|
||||
*/
|
||||
public IFile getPackageFile() {
|
||||
return mPackageFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if Android app is marked as debuggable in its manifest
|
||||
*/
|
||||
public Boolean getDebuggable() {
|
||||
return mDebuggable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the required api version number for the Android app
|
||||
*/
|
||||
public int getRequiredApiVersionNumber() {
|
||||
return mRequiredApiVersionNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param retryMode the install retry mode to set
|
||||
*/
|
||||
public void setRetryMode(InstallRetryMode retryMode) {
|
||||
this.mRetryMode = retryMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the installation retry mode
|
||||
*/
|
||||
public InstallRetryMode getRetryMode() {
|
||||
return mRetryMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the launch action
|
||||
*/
|
||||
public IAndroidLaunchAction getLaunchAction() {
|
||||
return mLaunchAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the launch
|
||||
*/
|
||||
public AndroidLaunch getLaunch() {
|
||||
return mLaunch;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the launch progress monitor
|
||||
*/
|
||||
public IProgressMonitor getMonitor() {
|
||||
return mMonitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param debugMode the debug mode to set
|
||||
*/
|
||||
public void setDebugMode(boolean debugMode) {
|
||||
this.mDebugMode = debugMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this is a debug launch
|
||||
*/
|
||||
public boolean isDebugMode() {
|
||||
return mDebugMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases the number of launch attempts
|
||||
*/
|
||||
public void incrementAttemptCount() {
|
||||
mAttemptCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of launch attempts made
|
||||
*/
|
||||
public int getAttemptCount() {
|
||||
return mAttemptCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if launch has been cancelled
|
||||
*/
|
||||
public void setCancelled(boolean cancelled) {
|
||||
this.mCancelled = cancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if launch has been cancelled
|
||||
*/
|
||||
public boolean isCancelled() {
|
||||
return mCancelled;
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.debug.launching;
|
||||
package com.android.ide.eclipse.adt.launch;
|
||||
|
||||
import com.android.ddmlib.AndroidDebugBridge;
|
||||
import com.android.ddmlib.Client;
|
||||
@@ -26,21 +26,18 @@ import com.android.ddmuilib.IImageLoader;
|
||||
import com.android.ddmuilib.ImageHelper;
|
||||
import com.android.ddmuilib.TableHelper;
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.debug.launching.AndroidLaunchController.AndroidLaunchConfiguration;
|
||||
import com.android.ide.eclipse.adt.debug.launching.AndroidLaunchController.DelayedLaunchInfo;
|
||||
import com.android.ide.eclipse.adt.sdk.Sdk;
|
||||
import com.android.ide.eclipse.ddms.DdmsPlugin;
|
||||
import com.android.sdklib.IAndroidTarget;
|
||||
import com.android.sdklib.vm.VmManager.VmInfo;
|
||||
import com.android.sdklib.avd.AvdManager;
|
||||
import com.android.sdklib.avd.AvdManager.AvdInfo;
|
||||
import com.android.sdkuilib.AvdSelector;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.jface.dialogs.Dialog;
|
||||
import org.eclipse.jface.dialogs.IDialogConstants;
|
||||
import org.eclipse.jface.preference.IPreferenceStore;
|
||||
import org.eclipse.jface.viewers.DoubleClickEvent;
|
||||
import org.eclipse.jface.viewers.IDoubleClickListener;
|
||||
import org.eclipse.jface.viewers.ILabelProviderListener;
|
||||
import org.eclipse.jface.viewers.ISelection;
|
||||
import org.eclipse.jface.viewers.IStructuredContentProvider;
|
||||
import org.eclipse.jface.viewers.IStructuredSelection;
|
||||
import org.eclipse.jface.viewers.ITableLabelProvider;
|
||||
import org.eclipse.jface.viewers.StructuredSelection;
|
||||
import org.eclipse.jface.viewers.TableViewer;
|
||||
@@ -50,33 +47,36 @@ import org.eclipse.swt.SWTException;
|
||||
import org.eclipse.swt.events.SelectionAdapter;
|
||||
import org.eclipse.swt.events.SelectionEvent;
|
||||
import org.eclipse.swt.graphics.Image;
|
||||
import org.eclipse.swt.graphics.Rectangle;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Button;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Dialog;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
import org.eclipse.swt.widgets.Event;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.Listener;
|
||||
import org.eclipse.swt.widgets.Shell;
|
||||
import org.eclipse.swt.widgets.Table;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A dialog that lets the user choose a device to deploy an application.
|
||||
* The user can either choose an exiting running device (including running emulators)
|
||||
* or start a new emulator using an Android Virtual Device configuration that matches
|
||||
* the current project.
|
||||
*/
|
||||
public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener {
|
||||
|
||||
private final static int DLG_WIDTH = 500;
|
||||
private final static int DLG_HEIGHT = 300;
|
||||
private final static int ICON_WIDTH = 16;
|
||||
|
||||
private final static String PREFS_COL_SERIAL = "deviceChooser.serial"; //$NON-NLS-1$
|
||||
private final static String PREFS_COL_STATE = "deviceChooser.state"; //$NON-NLS-1$
|
||||
private final static String PREFS_COL_VM = "deviceChooser.vm"; //$NON-NLS-1$
|
||||
private final static String PREFS_COL_STATE = "deviceChooser.state"; //$NON-NLS-1$
|
||||
private final static String PREFS_COL_AVD = "deviceChooser.avd"; //$NON-NLS-1$
|
||||
private final static String PREFS_COL_TARGET = "deviceChooser.target"; //$NON-NLS-1$
|
||||
private final static String PREFS_COL_DEBUG = "deviceChooser.debug"; //$NON-NLS-1$
|
||||
private final static String PREFS_COL_DEBUG = "deviceChooser.debug"; //$NON-NLS-1$
|
||||
|
||||
private Table mDeviceTable;
|
||||
private TableViewer mViewer;
|
||||
private AvdSelector mPreferredAvdSelector;
|
||||
|
||||
private Image mDeviceImage;
|
||||
private Image mEmulatorImage;
|
||||
@@ -84,13 +84,16 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
|
||||
private Image mNoMatchImage;
|
||||
private Image mWarningImage;
|
||||
|
||||
private Button mOkButton;
|
||||
private Button mCreateButton;
|
||||
|
||||
private DeviceChooserResponse mResponse;
|
||||
private DelayedLaunchInfo mLaunchInfo;
|
||||
private IAndroidTarget mProjectTarget;
|
||||
private Sdk mSdk;
|
||||
private final DeviceChooserResponse mResponse;
|
||||
private final String mPackageName;
|
||||
private final IAndroidTarget mProjectTarget;
|
||||
private final Sdk mSdk;
|
||||
|
||||
private final AvdInfo[] mFullAvdList;
|
||||
|
||||
private Button mDeviceRadioButton;
|
||||
|
||||
private boolean mDisableAvdSelectionChange = false;
|
||||
|
||||
/**
|
||||
* Basic Content Provider for a table full of {@link Device} objects. The input is
|
||||
@@ -135,22 +138,26 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
|
||||
try {
|
||||
String apiValue = device.getProperty(
|
||||
IDevice.PROP_BUILD_VERSION_NUMBER);
|
||||
int api = Integer.parseInt(apiValue);
|
||||
if (api >= mProjectTarget.getApiVersionNumber()) {
|
||||
// if the project is compiling against an add-on, the optional
|
||||
// API may be missing from the device.
|
||||
return mProjectTarget.isPlatform() ?
|
||||
mMatchImage : mWarningImage;
|
||||
if (apiValue != null) {
|
||||
int api = Integer.parseInt(apiValue);
|
||||
if (api >= mProjectTarget.getApiVersionNumber()) {
|
||||
// if the project is compiling against an add-on, the optional
|
||||
// API may be missing from the device.
|
||||
return mProjectTarget.isPlatform() ?
|
||||
mMatchImage : mWarningImage;
|
||||
} else {
|
||||
return mNoMatchImage;
|
||||
}
|
||||
} else {
|
||||
return mNoMatchImage;
|
||||
return mWarningImage;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
// lets consider the device non compatible
|
||||
return mNoMatchImage;
|
||||
}
|
||||
} else {
|
||||
// get the VmInfo
|
||||
VmInfo info = mSdk.getVmManager().getVm(device.getVmName());
|
||||
// get the AvdInfo
|
||||
AvdInfo info = mSdk.getAvdManager().getAvd(device.getAvdName());
|
||||
if (info == null) {
|
||||
return mWarningImage;
|
||||
}
|
||||
@@ -171,19 +178,23 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
|
||||
return device.getSerialNumber();
|
||||
case 1:
|
||||
if (device.isEmulator()) {
|
||||
return device.getVmName();
|
||||
return device.getAvdName();
|
||||
} else {
|
||||
return "N/A"; // devices don't have VM names.
|
||||
return "N/A"; // devices don't have AVD names.
|
||||
}
|
||||
case 2:
|
||||
if (device.isEmulator()) {
|
||||
VmInfo info = mSdk.getVmManager().getVm(device.getVmName());
|
||||
AvdInfo info = mSdk.getAvdManager().getAvd(device.getAvdName());
|
||||
if (info == null) {
|
||||
return "?";
|
||||
}
|
||||
return info.getTarget().getFullName();
|
||||
} else {
|
||||
return device.getProperty(IDevice.PROP_BUILD_VERSION);
|
||||
String deviceBuild = device.getProperty(IDevice.PROP_BUILD_VERSION);
|
||||
if (deviceBuild == null) {
|
||||
return "unknown";
|
||||
}
|
||||
return deviceBuild;
|
||||
}
|
||||
case 3:
|
||||
String debuggable = device.getProperty(IDevice.PROP_DEBUGGABLE);
|
||||
@@ -219,62 +230,48 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
|
||||
}
|
||||
|
||||
public static class DeviceChooserResponse {
|
||||
public boolean mustContinue;
|
||||
public boolean mustLaunchEmulator;
|
||||
public VmInfo vmToLaunch;
|
||||
public Device deviceToUse;
|
||||
}
|
||||
|
||||
public DeviceChooserDialog(Shell parent) {
|
||||
super(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare and display the dialog.
|
||||
* @param response
|
||||
* @param project
|
||||
* @param projectTarget
|
||||
* @param launch
|
||||
* @param launchInfo
|
||||
* @param config
|
||||
*/
|
||||
public void open(DeviceChooserResponse response, IProject project,
|
||||
IAndroidTarget projectTarget, AndroidLaunch launch, DelayedLaunchInfo launchInfo,
|
||||
AndroidLaunchConfiguration config) {
|
||||
mResponse = response;
|
||||
mProjectTarget = projectTarget;
|
||||
mLaunchInfo = launchInfo;
|
||||
mSdk = Sdk.getCurrent();
|
||||
|
||||
Shell parent = getParent();
|
||||
Shell shell = new Shell(parent, getStyle());
|
||||
shell.setText("Device Chooser");
|
||||
|
||||
loadImages();
|
||||
createContents(shell);
|
||||
private AvdInfo mAvdToLaunch;
|
||||
private IDevice mDeviceToUse;
|
||||
|
||||
// Set the dialog size.
|
||||
shell.setMinimumSize(DLG_WIDTH, DLG_HEIGHT);
|
||||
Rectangle r = parent.getBounds();
|
||||
// get the center new top left.
|
||||
int cx = r.x + r.width/2;
|
||||
int x = cx - DLG_WIDTH / 2;
|
||||
int cy = r.y + r.height/2;
|
||||
int y = cy - DLG_HEIGHT / 2;
|
||||
shell.setBounds(x, y, DLG_WIDTH, DLG_HEIGHT);
|
||||
|
||||
shell.pack();
|
||||
shell.open();
|
||||
|
||||
// start the listening.
|
||||
AndroidDebugBridge.addDeviceChangeListener(this);
|
||||
|
||||
Display display = parent.getDisplay();
|
||||
while (!shell.isDisposed()) {
|
||||
if (!display.readAndDispatch())
|
||||
display.sleep();
|
||||
public void setDeviceToUse(IDevice d) {
|
||||
mDeviceToUse = d;
|
||||
mAvdToLaunch = null;
|
||||
}
|
||||
|
||||
public void setAvdToLaunch(AvdInfo avd) {
|
||||
mAvdToLaunch = avd;
|
||||
mDeviceToUse = null;
|
||||
}
|
||||
|
||||
public IDevice getDeviceToUse() {
|
||||
return mDeviceToUse;
|
||||
}
|
||||
|
||||
public AvdInfo getAvdToLaunch() {
|
||||
return mAvdToLaunch;
|
||||
}
|
||||
}
|
||||
|
||||
public DeviceChooserDialog(Shell parent, DeviceChooserResponse response, String packageName,
|
||||
IAndroidTarget projectTarget) {
|
||||
super(parent);
|
||||
mResponse = response;
|
||||
mPackageName = packageName;
|
||||
mProjectTarget = projectTarget;
|
||||
mSdk = Sdk.getCurrent();
|
||||
|
||||
// get the full list of Android Virtual Devices
|
||||
AvdManager avdManager = mSdk.getAvdManager();
|
||||
if (avdManager != null) {
|
||||
mFullAvdList = avdManager.getAvds();
|
||||
} else {
|
||||
mFullAvdList = null;
|
||||
}
|
||||
|
||||
loadImages();
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
// done listening.
|
||||
AndroidDebugBridge.removeDeviceChangeListener(this);
|
||||
|
||||
@@ -283,30 +280,73 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
|
||||
mMatchImage.dispose();
|
||||
mNoMatchImage.dispose();
|
||||
mWarningImage.dispose();
|
||||
|
||||
AndroidLaunchController.getInstance().continueLaunch(response, project, launch,
|
||||
launchInfo, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the device chooser dialog contents.
|
||||
* @param shell the parent shell.
|
||||
*/
|
||||
private void createContents(final Shell shell) {
|
||||
shell.setLayout(new GridLayout(1, true));
|
||||
@Override
|
||||
protected void okPressed() {
|
||||
cleanup();
|
||||
super.okPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelPressed() {
|
||||
cleanup();
|
||||
super.cancelPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Control createContents(Composite parent) {
|
||||
Control content = super.createContents(parent);
|
||||
|
||||
// this must be called after createContents() has happened so that the
|
||||
// ok button has been created (it's created after the call to createDialogArea)
|
||||
updateDefaultSelection();
|
||||
|
||||
shell.addListener(SWT.Close, new Listener() {
|
||||
public void handleEvent(Event event) {
|
||||
event.doit = true;
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Control createDialogArea(Composite parent) {
|
||||
Composite top = new Composite(parent, SWT.NONE);
|
||||
top.setLayout(new GridLayout(1, true));
|
||||
|
||||
mDeviceRadioButton = new Button(top, SWT.RADIO);
|
||||
mDeviceRadioButton.setText("Choose a running Android device");
|
||||
mDeviceRadioButton.addSelectionListener(new SelectionAdapter() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent e) {
|
||||
boolean deviceMode = mDeviceRadioButton.getSelection();
|
||||
|
||||
mDeviceTable.setEnabled(deviceMode);
|
||||
mPreferredAvdSelector.setEnabled(!deviceMode);
|
||||
|
||||
if (deviceMode) {
|
||||
handleDeviceSelection();
|
||||
} else {
|
||||
mResponse.setAvdToLaunch(mPreferredAvdSelector.getFirstSelected());
|
||||
}
|
||||
|
||||
enableOkButton();
|
||||
}
|
||||
});
|
||||
mDeviceRadioButton.setSelection(true);
|
||||
|
||||
Label l = new Label(shell, SWT.NONE);
|
||||
l.setText("Select the target device.");
|
||||
|
||||
// offset the selector from the radio button
|
||||
Composite offsetComp = new Composite(top, SWT.NONE);
|
||||
offsetComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
GridLayout layout = new GridLayout(1, false);
|
||||
layout.marginRight = layout.marginHeight = 0;
|
||||
layout.marginLeft = 30;
|
||||
offsetComp.setLayout(layout);
|
||||
|
||||
IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
|
||||
mDeviceTable = new Table(shell, SWT.SINGLE | SWT.FULL_SELECTION);
|
||||
mDeviceTable.setLayoutData(new GridData(GridData.FILL_BOTH));
|
||||
mDeviceTable = new Table(offsetComp, SWT.SINGLE | SWT.FULL_SELECTION);
|
||||
GridData gd;
|
||||
mDeviceTable.setLayoutData(gd = new GridData(GridData.FILL_BOTH));
|
||||
gd.heightHint = 100;
|
||||
|
||||
mDeviceTable.setHeaderVisible(true);
|
||||
mDeviceTable.setLinesVisible(true);
|
||||
|
||||
@@ -314,9 +354,9 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
|
||||
SWT.LEFT, "AAA+AAAAAAAAAAAAAAAAAAA", //$NON-NLS-1$
|
||||
PREFS_COL_SERIAL, store);
|
||||
|
||||
TableHelper.createTableColumn(mDeviceTable, "VM Name",
|
||||
TableHelper.createTableColumn(mDeviceTable, "AVD Name",
|
||||
SWT.LEFT, "engineering", //$NON-NLS-1$
|
||||
PREFS_COL_VM, store);
|
||||
PREFS_COL_AVD, store);
|
||||
|
||||
TableHelper.createTableColumn(mDeviceTable, "Target",
|
||||
SWT.LEFT, "AAA+Android 9.9.9", //$NON-NLS-1$
|
||||
@@ -335,98 +375,78 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
|
||||
mViewer.setContentProvider(new ContentProvider());
|
||||
mViewer.setLabelProvider(new LabelProvider());
|
||||
mViewer.setInput(AndroidDebugBridge.getBridge());
|
||||
mViewer.addDoubleClickListener(new IDoubleClickListener() {
|
||||
public void doubleClick(DoubleClickEvent event) {
|
||||
ISelection selection = event.getSelection();
|
||||
if (selection instanceof IStructuredSelection) {
|
||||
IStructuredSelection structuredSelection = (IStructuredSelection)selection;
|
||||
Object object = structuredSelection.getFirstElement();
|
||||
if (object instanceof Device) {
|
||||
Device selectedDevice = (Device)object;
|
||||
|
||||
mResponse.deviceToUse = selectedDevice;
|
||||
mResponse.mustContinue = true;
|
||||
shell.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// bottom part with the ok/cancel
|
||||
Composite bottomComp = new Composite(shell, SWT.NONE);
|
||||
bottomComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
// 3 items in the layout: createButton, spacer, composite with ok/cancel
|
||||
// (to force same width).
|
||||
bottomComp.setLayout(new GridLayout(3 /* numColums */, false /* makeColumnsEqualWidth */));
|
||||
|
||||
mCreateButton = new Button(bottomComp, SWT.NONE);
|
||||
mCreateButton.setText("Launch Emulator");
|
||||
mCreateButton.addSelectionListener(new SelectionAdapter() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent e) {
|
||||
mResponse.mustContinue = true;
|
||||
mResponse.mustLaunchEmulator = true;
|
||||
shell.close();
|
||||
}
|
||||
});
|
||||
|
||||
// the spacer
|
||||
Composite spacer = new Composite(bottomComp, SWT.NONE);
|
||||
GridData gd;
|
||||
spacer.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
gd.heightHint = 0;
|
||||
|
||||
// the composite to contain ok/cancel
|
||||
Composite buttonContainer = new Composite(bottomComp, SWT.NONE);
|
||||
GridLayout gl = new GridLayout(2 /* numColums */, true /* makeColumnsEqualWidth */);
|
||||
gl.marginHeight = gl.marginWidth = 0;
|
||||
buttonContainer.setLayout(gl);
|
||||
|
||||
mOkButton = new Button(buttonContainer, SWT.NONE);
|
||||
mOkButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
mOkButton.setEnabled(false);
|
||||
mOkButton.setText("OK");
|
||||
mOkButton.addSelectionListener(new SelectionAdapter() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent e) {
|
||||
mResponse.mustContinue = true;
|
||||
shell.close();
|
||||
}
|
||||
});
|
||||
|
||||
Button cancelButton = new Button(buttonContainer, SWT.NONE);
|
||||
cancelButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
cancelButton.setText("Cancel");
|
||||
cancelButton.addSelectionListener(new SelectionAdapter() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent e) {
|
||||
mResponse.mustContinue = false;
|
||||
shell.close();
|
||||
}
|
||||
});
|
||||
|
||||
mDeviceTable.addSelectionListener(new SelectionAdapter() {
|
||||
/**
|
||||
* Handles single-click selection on the device selector.
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent e) {
|
||||
int count = mDeviceTable.getSelectionCount();
|
||||
if (count != 1) {
|
||||
handleSelection(null);
|
||||
} else {
|
||||
int index = mDeviceTable.getSelectionIndex();
|
||||
Object data = mViewer.getElementAt(index);
|
||||
if (data instanceof Device) {
|
||||
handleSelection((Device)data);
|
||||
} else {
|
||||
handleSelection(null);
|
||||
}
|
||||
handleDeviceSelection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles double-click selection on the device selector.
|
||||
* Note that the single-click handler will probably already have been called.
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void widgetDefaultSelected(SelectionEvent e) {
|
||||
handleDeviceSelection();
|
||||
if (isOkButtonEnabled()) {
|
||||
okPressed();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mDeviceTable.setFocus();
|
||||
shell.setDefaultButton(mOkButton);
|
||||
Button radio2 = new Button(top, SWT.RADIO);
|
||||
radio2.setText("Launch a new Android Virtual Device");
|
||||
|
||||
// offset the selector from the radio button
|
||||
offsetComp = new Composite(top, SWT.NONE);
|
||||
offsetComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
layout = new GridLayout(1, false);
|
||||
layout.marginRight = layout.marginHeight = 0;
|
||||
layout.marginLeft = 30;
|
||||
offsetComp.setLayout(layout);
|
||||
|
||||
updateDefaultSelection();
|
||||
mPreferredAvdSelector = new AvdSelector(offsetComp, getNonRunningAvds(), mProjectTarget);
|
||||
mPreferredAvdSelector.setTableHeightHint(100);
|
||||
mPreferredAvdSelector.setEnabled(false);
|
||||
mPreferredAvdSelector.setSelectionListener(new SelectionAdapter() {
|
||||
/**
|
||||
* Handles single-click selection on the AVD selector.
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent e) {
|
||||
if (mDisableAvdSelectionChange == false) {
|
||||
mResponse.setAvdToLaunch(mPreferredAvdSelector.getFirstSelected());
|
||||
enableOkButton();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles double-click selection on the AVD selector.
|
||||
*
|
||||
* Note that the single-click handler will probably already have been called
|
||||
* but the selected item can have changed in between.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void widgetDefaultSelected(SelectionEvent e) {
|
||||
widgetSelected(e);
|
||||
if (isOkButtonEnabled()) {
|
||||
okPressed();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AndroidDebugBridge.addDeviceChangeListener(this);
|
||||
|
||||
return top;
|
||||
}
|
||||
|
||||
private void loadImages() {
|
||||
@@ -504,6 +524,10 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
|
||||
|
||||
// update the selection
|
||||
updateDefaultSelection();
|
||||
|
||||
// update the display of AvdInfo (since it's filtered to only display
|
||||
// non running AVD.)
|
||||
refillAvdList();
|
||||
} else {
|
||||
// table is disposed, we need to do something.
|
||||
// lets remove ourselves from the listener.
|
||||
@@ -546,23 +570,57 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
|
||||
|
||||
// update the defaultSelection.
|
||||
updateDefaultSelection();
|
||||
|
||||
|
||||
// update the display of AvdInfo (since it's filtered to only display
|
||||
// non running AVD). This is done on deviceChanged because the avd name
|
||||
// of a (emulator) device may be updated as the emulator boots.
|
||||
refillAvdList();
|
||||
|
||||
// if the changed device is the current selection,
|
||||
// we update the OK button based on its state.
|
||||
if (device == mResponse.deviceToUse) {
|
||||
mOkButton.setEnabled(mResponse.deviceToUse.isOnline());
|
||||
if (device == mResponse.getDeviceToUse()) {
|
||||
enableOkButton();
|
||||
}
|
||||
|
||||
} else {
|
||||
// table is disposed, we need to do something.
|
||||
// lets remove ourselves from the listener.
|
||||
AndroidDebugBridge.removeDeviceChangeListener(dialog);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the dialog is in "device" mode (true), or in "avd" mode (false).
|
||||
*/
|
||||
private boolean isDeviceMode() {
|
||||
return mDeviceRadioButton.getSelection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables the OK button of the dialog based on various selections in the dialog.
|
||||
*/
|
||||
private void enableOkButton() {
|
||||
Button okButton = getButton(IDialogConstants.OK_ID);
|
||||
|
||||
if (isDeviceMode()) {
|
||||
okButton.setEnabled(mResponse.getDeviceToUse() != null &&
|
||||
mResponse.getDeviceToUse().isOnline());
|
||||
} else {
|
||||
okButton.setEnabled(mResponse.getAvdToLaunch() != null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the ok button is enabled.
|
||||
*/
|
||||
private boolean isOkButtonEnabled() {
|
||||
Button okButton = getButton(IDialogConstants.OK_ID);
|
||||
return okButton.isEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the {@link Runnable} in the UI thread.
|
||||
* @param runnable the runnable to execute.
|
||||
@@ -577,16 +635,31 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDeviceSelection() {
|
||||
int count = mDeviceTable.getSelectionCount();
|
||||
if (count != 1) {
|
||||
handleSelection(null);
|
||||
} else {
|
||||
int index = mDeviceTable.getSelectionIndex();
|
||||
Object data = mViewer.getElementAt(index);
|
||||
if (data instanceof Device) {
|
||||
handleSelection((Device)data);
|
||||
} else {
|
||||
handleSelection(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSelection(Device device) {
|
||||
mResponse.deviceToUse = device;
|
||||
mOkButton.setEnabled(device != null && mResponse.deviceToUse.isOnline());
|
||||
mResponse.setDeviceToUse(device);
|
||||
enableOkButton();
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for a default device to select. This is done by looking for the running
|
||||
* clients on each device and finding one similar to the one being launched.
|
||||
* <p/>
|
||||
* This is done every time the device list changed, until there is a selection..
|
||||
* This is done every time the device list changed unless there is a already selection.
|
||||
*/
|
||||
private void updateDefaultSelection() {
|
||||
if (mDeviceTable.getSelectionCount() == 0) {
|
||||
@@ -599,8 +672,7 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
|
||||
|
||||
for (Client client : clients) {
|
||||
|
||||
if (mLaunchInfo.mPackageName.equals(
|
||||
client.getClientData().getClientDescription())) {
|
||||
if (mPackageName.equals(client.getClientData().getClientDescription())) {
|
||||
// found a match! Select it.
|
||||
mViewer.setSelection(new StructuredSelection(device));
|
||||
handleSelection(device);
|
||||
@@ -611,6 +683,58 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleDeviceSelection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of {@link AvdInfo} that are not already running in an emulator.
|
||||
*/
|
||||
private AvdInfo[] getNonRunningAvds() {
|
||||
ArrayList<AvdInfo> list = new ArrayList<AvdInfo>();
|
||||
|
||||
Device[] devices = AndroidDebugBridge.getBridge().getDevices();
|
||||
|
||||
// loop through all the Avd and put the one that are not running in the list.
|
||||
avdLoop: for (AvdInfo info : mFullAvdList) {
|
||||
for (Device d : devices) {
|
||||
if (info.getName().equals(d.getAvdName())) {
|
||||
continue avdLoop;
|
||||
}
|
||||
}
|
||||
list.add(info);
|
||||
}
|
||||
|
||||
return list.toArray(new AvdInfo[list.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refills the AVD list keeping the current selection.
|
||||
*/
|
||||
private void refillAvdList() {
|
||||
AvdInfo[] array = getNonRunningAvds();
|
||||
|
||||
// save the current selection
|
||||
AvdInfo selected = mPreferredAvdSelector.getFirstSelected();
|
||||
|
||||
// disable selection change.
|
||||
mDisableAvdSelectionChange = true;
|
||||
|
||||
// set the new list in the selector
|
||||
mPreferredAvdSelector.setAvds(array, mProjectTarget);
|
||||
|
||||
// attempt to reselect the proper avd if needed
|
||||
if (selected != null) {
|
||||
if (mPreferredAvdSelector.setSelection(selected) == false) {
|
||||
// looks like the selection is lost. this can happen if an emulator
|
||||
// running the AVD that was selected was launched from outside of Eclipse).
|
||||
mResponse.setAvdToLaunch(null);
|
||||
enableOkButton();
|
||||
}
|
||||
}
|
||||
|
||||
// enable the selection change
|
||||
mDisableAvdSelectionChange = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.ide.eclipse.adt.launch;
|
||||
|
||||
import com.android.ddmlib.IDevice;
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
|
||||
/**
|
||||
* A launch action that does nothing after the application has been installed
|
||||
*/
|
||||
public class EmptyLaunchAction implements IAndroidLaunchAction {
|
||||
|
||||
public boolean doLaunchAction(DelayedLaunchInfo info, IDevice device) {
|
||||
// we're not supposed to do anything, just return;
|
||||
String msg = String.format("%1$s installed on device",
|
||||
info.getPackageFile().getFullPath().toOSString());
|
||||
AdtPlugin.printToConsole(info.getProject(), msg, "Done!");
|
||||
// return false so launch controller will not wait for debugger to attach
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getLaunchDescription() {
|
||||
return "sync";
|
||||
}
|
||||
}
|
||||
@@ -14,17 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.debug.ui;
|
||||
package com.android.ide.eclipse.adt.launch;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.debug.launching.LaunchConfigDelegate;
|
||||
import com.android.ide.eclipse.adt.sdk.Sdk;
|
||||
import com.android.ide.eclipse.common.project.BaseProjectHelper;
|
||||
import com.android.ide.eclipse.ddms.DdmsPlugin;
|
||||
import com.android.sdklib.IAndroidTarget;
|
||||
import com.android.sdklib.vm.VmManager;
|
||||
import com.android.sdklib.vm.VmManager.VmInfo;
|
||||
import com.android.sdkuilib.VmSelector;
|
||||
import com.android.sdklib.avd.AvdManager;
|
||||
import com.android.sdklib.avd.AvdManager.AvdInfo;
|
||||
import com.android.sdkuilib.AvdSelector;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
@@ -75,7 +74,7 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
|
||||
private Button mAutoTargetButton;
|
||||
private Button mManualTargetButton;
|
||||
|
||||
private VmSelector mPreferredVmSelector;
|
||||
private AvdSelector mPreferredAvdSelector;
|
||||
|
||||
private Combo mSpeedCombo;
|
||||
|
||||
@@ -89,6 +88,8 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
|
||||
|
||||
private Button mNoBootAnimButton;
|
||||
|
||||
private Label mPreferredAvdLabel;
|
||||
|
||||
/**
|
||||
* Returns the emulator ready speed option value.
|
||||
* @param value The index of the combo selection.
|
||||
@@ -160,14 +161,26 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent e) {
|
||||
updateLaunchConfigurationDialog();
|
||||
|
||||
boolean auto = mAutoTargetButton.getSelection();
|
||||
mPreferredAvdSelector.setEnabled(auto);
|
||||
mPreferredAvdLabel.setEnabled(auto);
|
||||
}
|
||||
});
|
||||
|
||||
new Label(targetModeGroup, SWT.NONE).setText("Preferred VM");
|
||||
VmInfo[] vms = new VmInfo[0];
|
||||
mPreferredVmSelector = new VmSelector(targetModeGroup, vms,
|
||||
false /*allowMultipleSelection*/);
|
||||
mPreferredVmSelector.setSelectionListener(new SelectionAdapter() {
|
||||
Composite offsetComp = new Composite(targetModeGroup, SWT.NONE);
|
||||
offsetComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
layout = new GridLayout(1, false);
|
||||
layout.marginRight = layout.marginHeight = 0;
|
||||
layout.marginLeft = 30;
|
||||
offsetComp.setLayout(layout);
|
||||
|
||||
mPreferredAvdLabel = new Label(offsetComp, SWT.NONE);
|
||||
mPreferredAvdLabel.setText("Select a preferred Android Virtual Device:");
|
||||
AvdInfo[] avds = new AvdInfo[0];
|
||||
mPreferredAvdSelector = new AvdSelector(offsetComp, avds);
|
||||
mPreferredAvdSelector.setTableHeightHint(100);
|
||||
mPreferredAvdSelector.setSelectionListener(new SelectionAdapter() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent e) {
|
||||
updateLaunchConfigurationDialog();
|
||||
@@ -277,7 +290,7 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
|
||||
* @see org.eclipse.debug.ui.ILaunchConfigurationTab#initializeFrom(org.eclipse.debug.core.ILaunchConfiguration)
|
||||
*/
|
||||
public void initializeFrom(ILaunchConfiguration configuration) {
|
||||
VmManager vmManager = Sdk.getCurrent().getVmManager();
|
||||
AvdManager avdManager = Sdk.getCurrent().getAvdManager();
|
||||
|
||||
boolean value = LaunchConfigDelegate.DEFAULT_TARGET_MODE; // true == automatic
|
||||
try {
|
||||
@@ -311,34 +324,34 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
|
||||
}
|
||||
}
|
||||
|
||||
// update the VM list
|
||||
VmInfo[] vms = null;
|
||||
if (vmManager != null) {
|
||||
vms = vmManager.getVms();
|
||||
// update the AVD list
|
||||
AvdInfo[] avds = null;
|
||||
if (avdManager != null) {
|
||||
avds = avdManager.getAvds();
|
||||
}
|
||||
|
||||
IAndroidTarget projectTarget = null;
|
||||
if (project != null) {
|
||||
projectTarget = Sdk.getCurrent().getTarget(project);
|
||||
} else {
|
||||
vms = null; // no project? we don't want to display any "compatible" VMs.
|
||||
avds = null; // no project? we don't want to display any "compatible" AVDs.
|
||||
}
|
||||
|
||||
mPreferredVmSelector.setVms(vms, projectTarget);
|
||||
mPreferredAvdSelector.setAvds(avds, projectTarget);
|
||||
|
||||
stringValue = "";
|
||||
try {
|
||||
stringValue = configuration.getAttribute(LaunchConfigDelegate.ATTR_VM_NAME,
|
||||
stringValue = configuration.getAttribute(LaunchConfigDelegate.ATTR_AVD_NAME,
|
||||
stringValue);
|
||||
} catch (CoreException e) {
|
||||
// let's not do anything here, we'll use the default value
|
||||
}
|
||||
|
||||
if (stringValue != null && stringValue.length() > 0 && vmManager != null) {
|
||||
VmInfo targetVm = vmManager.getVm(stringValue);
|
||||
mPreferredVmSelector.setSelection(targetVm);
|
||||
if (stringValue != null && stringValue.length() > 0 && avdManager != null) {
|
||||
AvdInfo targetAvd = avdManager.getAvd(stringValue);
|
||||
mPreferredAvdSelector.setSelection(targetAvd);
|
||||
} else {
|
||||
mPreferredVmSelector.setSelection(null);
|
||||
mPreferredAvdSelector.setSelection(null);
|
||||
}
|
||||
|
||||
value = LaunchConfigDelegate.DEFAULT_WIPE_DATA;
|
||||
@@ -404,11 +417,11 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
|
||||
public void performApply(ILaunchConfigurationWorkingCopy configuration) {
|
||||
configuration.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE,
|
||||
mAutoTargetButton.getSelection());
|
||||
VmInfo vm = mPreferredVmSelector.getFirstSelected();
|
||||
if (vm != null) {
|
||||
configuration.setAttribute(LaunchConfigDelegate.ATTR_VM_NAME, vm.getName());
|
||||
AvdInfo avd = mPreferredAvdSelector.getFirstSelected();
|
||||
if (avd != null) {
|
||||
configuration.setAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, avd.getName());
|
||||
} else {
|
||||
configuration.setAttribute(LaunchConfigDelegate.ATTR_VM_NAME, (String)null);
|
||||
configuration.setAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, (String)null);
|
||||
}
|
||||
configuration.setAttribute(LaunchConfigDelegate.ATTR_SPEED,
|
||||
mSpeedCombo.getSelectionIndex());
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.launch;
|
||||
|
||||
import com.android.ddmlib.IDevice;
|
||||
import com.android.ide.eclipse.adt.launch.DelayedLaunchInfo;
|
||||
|
||||
/**
|
||||
* An action to perform after performing a launch of an Android application
|
||||
*/
|
||||
public interface IAndroidLaunchAction {
|
||||
|
||||
/**
|
||||
* Do the launch
|
||||
*
|
||||
* @param info the {@link DelayedLaunchInfo} that contains launch details
|
||||
* @param device the Android device to perform action on
|
||||
* @returns true if launch was successfully, and controller should wait for debugger to attach
|
||||
* (if applicable)
|
||||
*/
|
||||
boolean doLaunchAction(DelayedLaunchInfo info, IDevice device);
|
||||
|
||||
/**
|
||||
* Return a description of launch, to be used for logging and error messages
|
||||
*/
|
||||
String getLaunchDescription();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.launch;
|
||||
|
||||
import com.android.ddmlib.IDevice;
|
||||
|
||||
/**
|
||||
* Interface for managing Android launches
|
||||
*/
|
||||
public interface ILaunchController {
|
||||
|
||||
/**
|
||||
* Launches an application on a device or emulator
|
||||
*
|
||||
* @param launchInfo the {@link DelayedLaunchInfo} that indicates the launch action
|
||||
* @param device the device or emulator to launch the application on
|
||||
*/
|
||||
public void launchApp(DelayedLaunchInfo launchInfo, IDevice device);
|
||||
|
||||
/**
|
||||
* Cancels a launch
|
||||
*
|
||||
* @param launchInfo the {@link DelayedLaunchInfo} to cancel
|
||||
*/
|
||||
void stopLaunch(DelayedLaunchInfo launchInfo);
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.launch;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.sdklib.SdkConstants;
|
||||
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.FileLocator;
|
||||
import org.eclipse.core.runtime.Platform;
|
||||
import org.eclipse.debug.core.ILaunchConfiguration;
|
||||
import org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate;
|
||||
import org.osgi.framework.Bundle;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* For Android projects, android.jar gets added to the launch configuration of
|
||||
* JUnit tests as a bootstrap entry. This breaks JUnit tests as android.jar
|
||||
* contains a skeleton version of JUnit classes and the JVM will stop with an error similar
|
||||
* to: <blockquote> Error occurred during initialization of VM
|
||||
* java/lang/NoClassDefFoundError: java/lang/ref/FinalReference </blockquote>
|
||||
* <p>
|
||||
* At compile time, Eclipse does not know that there is no valid junit.jar in
|
||||
* the classpath since it can find a correct reference to all the necessary
|
||||
* org.junit.* classes in the android.jar so it does not prompt the user to add
|
||||
* the JUnit3 or JUnit4 jar.
|
||||
* <p>
|
||||
* This delegates removes the android.jar from the bootstrap path and if
|
||||
* necessary also puts back the junit.jar in the user classpath.
|
||||
* <p>
|
||||
* This delegate will be present for both Java and Android projects (delegates
|
||||
* setting instead of only the current project) but the behavior for Java
|
||||
* projects should be neutral since:
|
||||
* <ol>
|
||||
* <li>Java tests can only compile (and then run) when a valid junit.jar is
|
||||
* present
|
||||
* <li>There is no android.jar in Java projects
|
||||
* </ol>
|
||||
*/
|
||||
public class JUnitLaunchConfigDelegate extends JUnitLaunchConfigurationDelegate {
|
||||
|
||||
private static final String JUNIT_JAR = "junit.jar"; //$NON-NLS-1$
|
||||
|
||||
@Override
|
||||
public String[][] getBootpathExt(ILaunchConfiguration configuration) throws CoreException {
|
||||
String[][] bootpath = super.getBootpathExt(configuration);
|
||||
return fixBootpathExt(bootpath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getClasspath(ILaunchConfiguration configuration) throws CoreException {
|
||||
String[] classpath = super.getClasspath(configuration);
|
||||
return fixClasspath(classpath, getJavaProjectName(configuration));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the android.jar from the bootstrap path if present.
|
||||
*
|
||||
* @param bootpath Array of Arrays of bootstrap class paths
|
||||
* @return a new modified (if applicable) bootpath
|
||||
*/
|
||||
public static String[][] fixBootpathExt(String[][] bootpath) {
|
||||
for (int i = 0; i < bootpath.length; i++) {
|
||||
if (bootpath[i] != null) {
|
||||
// we assume that the android.jar can only be present in the
|
||||
// bootstrap path of android tests
|
||||
if (bootpath[i][0].endsWith(SdkConstants.FN_FRAMEWORK_LIBRARY)) {
|
||||
bootpath[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return bootpath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the junit.jar to the user classpath; since Eclipse was relying on
|
||||
* android.jar to provide the appropriate org.junit classes, it does not
|
||||
* know it actually needs the junit.jar.
|
||||
*
|
||||
* @param classpath Array containing classpath
|
||||
* @param projectName The name of the project (for logging purposes)
|
||||
*
|
||||
* @return a new modified (if applicable) classpath
|
||||
*/
|
||||
public static String[] fixClasspath(String[] classpath, String projectName) {
|
||||
// search for junit.jar; if any are found return immediately
|
||||
for (int i = 0; i < classpath.length; i++) {
|
||||
if (classpath[i].endsWith(JUNIT_JAR)) {
|
||||
return classpath;
|
||||
}
|
||||
}
|
||||
|
||||
// This delegate being called without a junit.jar present is only
|
||||
// possible for Android projects. In a non-Android project, the test
|
||||
// would not compile and would be unable to run.
|
||||
try {
|
||||
// junit4 is backward compatible with junit3 and they uses the
|
||||
// same junit.jar from bundle org.junit:
|
||||
// When a project has mixed JUnit3 and JUnit4 tests, if JUnit3 jar
|
||||
// is added first it is then replaced by the JUnit4 jar when user is
|
||||
// prompted to fix the JUnit4 test failure
|
||||
String jarLocation = getJunitJarLocation();
|
||||
// we extend the classpath by one element and append junit.jar
|
||||
String[] newClasspath = new String[classpath.length + 1];
|
||||
System.arraycopy(classpath, 0, newClasspath, 0, classpath.length);
|
||||
newClasspath[newClasspath.length - 1] = jarLocation;
|
||||
classpath = newClasspath;
|
||||
} catch (IOException e) {
|
||||
// This should not happen as we depend on the org.junit
|
||||
// plugin explicitly; the error is logged here so that the user can
|
||||
// trace back the cause when the test fails to run
|
||||
AdtPlugin.log(e, "Could not find a valid junit.jar");
|
||||
AdtPlugin.printErrorToConsole(projectName,
|
||||
"Could not find a valid junit.jar");
|
||||
// Return the classpath as-is (with no junit.jar) anyway because we
|
||||
// will let the actual launch config fails.
|
||||
}
|
||||
|
||||
return classpath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path of the junit jar in the highest version bundle.
|
||||
*
|
||||
* (This is public only so that the test can call it)
|
||||
*
|
||||
* @return the path as a string
|
||||
* @throws IOException
|
||||
*/
|
||||
public static String getJunitJarLocation() throws IOException {
|
||||
Bundle bundle = Platform.getBundle("org.junit"); //$NON-NLS-1$
|
||||
if (bundle == null) {
|
||||
throw new IOException("Cannot find org.junit bundle");
|
||||
}
|
||||
URL jarUrl = bundle.getEntry(AndroidConstants.WS_SEP + JUNIT_JAR);
|
||||
return FileLocator.resolve(jarUrl).getFile();
|
||||
}
|
||||
}
|
||||
@@ -14,11 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.debug.launching;
|
||||
package com.android.ide.eclipse.adt.launch;
|
||||
|
||||
import com.android.ddmlib.AndroidDebugBridge;
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.debug.launching.AndroidLaunchController.AndroidLaunchConfiguration;
|
||||
import com.android.ide.eclipse.adt.project.ProjectHelper;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.common.project.AndroidManifestParser;
|
||||
@@ -80,7 +79,7 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate {
|
||||
*/
|
||||
public static final String ATTR_ACTIVITY = AdtPlugin.PLUGIN_ID + ".activity"; //$NON-NLS-1$
|
||||
|
||||
public static final String ATTR_VM_NAME = AdtPlugin.PLUGIN_ID + ".vm"; //$NON-NLS-1$
|
||||
public static final String ATTR_AVD_NAME = AdtPlugin.PLUGIN_ID + ".avd"; //$NON-NLS-1$
|
||||
|
||||
public static final String ATTR_SPEED = AdtPlugin.PLUGIN_ID + ".speed"; //$NON-NLS-1$
|
||||
|
||||
@@ -233,7 +232,16 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate {
|
||||
return;
|
||||
}
|
||||
|
||||
String activityName = null;
|
||||
doLaunch(configuration, mode, monitor, project, androidLaunch, config, controller,
|
||||
applicationPackage, manifestParser);
|
||||
}
|
||||
|
||||
protected void doLaunch(ILaunchConfiguration configuration, String mode,
|
||||
IProgressMonitor monitor, IProject project, AndroidLaunch androidLaunch,
|
||||
AndroidLaunchConfiguration config, AndroidLaunchController controller,
|
||||
IFile applicationPackage, AndroidManifestParser manifestParser) {
|
||||
|
||||
String activityName = null;
|
||||
|
||||
if (config.mLaunchAction == ACTION_ACTIVITY) {
|
||||
// Get the activity name defined in the config
|
||||
@@ -292,11 +300,16 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
IAndroidLaunchAction launchAction = new EmptyLaunchAction();
|
||||
if (activityName != null) {
|
||||
launchAction = new ActivityLaunchAction(activityName, controller);
|
||||
}
|
||||
|
||||
// everything seems fine, we ask the launch controller to handle
|
||||
// the rest
|
||||
controller.launch(project, mode, applicationPackage, manifestParser.getPackage(),
|
||||
manifestParser.getDebuggable(), manifestParser.getApiLevelRequirement(),
|
||||
activityName, config, androidLaunch, monitor);
|
||||
launchAction, config, androidLaunch, monitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.debug.ui;
|
||||
package com.android.ide.eclipse.adt.launch;
|
||||
|
||||
import org.eclipse.debug.ui.AbstractLaunchConfigurationTabGroup;
|
||||
import org.eclipse.debug.ui.CommonTab;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.debug.launching;
|
||||
package com.android.ide.eclipse.adt.launch;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
@@ -14,11 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.debug.ui;
|
||||
package com.android.ide.eclipse.adt.launch;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.debug.launching.AndroidLaunchController;
|
||||
import com.android.ide.eclipse.adt.debug.launching.LaunchConfigDelegate;
|
||||
import com.android.ide.eclipse.common.project.AndroidManifestParser;
|
||||
import com.android.ide.eclipse.common.project.BaseProjectHelper;
|
||||
import com.android.ide.eclipse.common.project.ProjectChooserHelper;
|
||||
@@ -17,11 +17,19 @@
|
||||
package com.android.ide.eclipse.adt.preferences;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.sdk.Sdk;
|
||||
import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener;
|
||||
import com.android.sdklib.IAndroidTarget;
|
||||
import com.android.sdkuilib.SdkTargetSelector;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.jface.preference.DirectoryFieldEditor;
|
||||
import org.eclipse.jface.preference.FieldEditorPreferencePage;
|
||||
import org.eclipse.jface.resource.JFaceResources;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
import org.eclipse.ui.IWorkbench;
|
||||
import org.eclipse.ui.IWorkbenchPreferencePage;
|
||||
@@ -80,6 +88,9 @@ public class AndroidPreferencePage extends FieldEditorPreferencePage implements
|
||||
*/
|
||||
private static class SdkDirectoryFieldEditor extends DirectoryFieldEditor {
|
||||
|
||||
private SdkTargetSelector mTargetSelector;
|
||||
private TargetChangedListener mTargetChangeListener;
|
||||
|
||||
public SdkDirectoryFieldEditor(String name, String labelText, Composite parent) {
|
||||
super(name, labelText, parent);
|
||||
setEmptyStringAllowed(false);
|
||||
@@ -131,5 +142,68 @@ public class AndroidPreferencePage extends FieldEditorPreferencePage implements
|
||||
setValidateStrategy(VALIDATE_ON_KEY_STROKE);
|
||||
return super.getTextControl(parent);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* Method declared on StringFieldEditor (and FieldEditor).
|
||||
*/
|
||||
@Override
|
||||
protected void doFillIntoGrid(Composite parent, int numColumns) {
|
||||
super.doFillIntoGrid(parent, numColumns);
|
||||
|
||||
GridData gd;
|
||||
Label l = new Label(parent, SWT.NONE);
|
||||
l.setText("Note: The list of SDK Targets below is only reloaded once you hit 'Apply' or 'OK'.");
|
||||
gd = new GridData(GridData.FILL_HORIZONTAL);
|
||||
gd.horizontalSpan = numColumns;
|
||||
l.setLayoutData(gd);
|
||||
|
||||
try {
|
||||
// We may not have an sdk if the sdk path pref is empty or not valid.
|
||||
Sdk sdk = Sdk.getCurrent();
|
||||
IAndroidTarget[] targets = sdk != null ? sdk.getTargets() : null;
|
||||
|
||||
mTargetSelector = new SdkTargetSelector(parent,
|
||||
targets,
|
||||
false, /*allowSelection*/
|
||||
false /*multipleSelection*/);
|
||||
gd = (GridData) mTargetSelector.getLayoutData();
|
||||
gd.horizontalSpan = numColumns;
|
||||
|
||||
if (mTargetChangeListener == null) {
|
||||
mTargetChangeListener = new TargetChangedListener();
|
||||
AdtPlugin.getDefault().addTargetListener(mTargetChangeListener);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// We need to catch *any* exception that arises here, otherwise it disables
|
||||
// the whole pref panel. We can live without the Sdk target selector but
|
||||
// not being able to actually set an sdk path.
|
||||
AdtPlugin.log(e, "SdkTargetSelector failed");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
if (mTargetChangeListener != null) {
|
||||
AdtPlugin.getDefault().removeTargetListener(mTargetChangeListener);
|
||||
mTargetChangeListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
private class TargetChangedListener implements ITargetChangeListener {
|
||||
public void onProjectTargetChange(IProject changedProject) {
|
||||
// do nothing.
|
||||
}
|
||||
|
||||
public void onTargetsLoaded() {
|
||||
if (mTargetSelector != null) {
|
||||
// We may not have an sdk if the sdk path pref is empty or not valid.
|
||||
Sdk sdk = Sdk.getCurrent();
|
||||
IAndroidTarget[] targets = sdk != null ? sdk.getTargets() : null;
|
||||
|
||||
mTargetSelector.setTargets(targets);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.project;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IAdaptable;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.eclipse.core.runtime.jobs.Job;
|
||||
import org.eclipse.jdt.core.ICompilationUnit;
|
||||
import org.eclipse.jdt.core.IJavaElement;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jdt.core.IPackageFragment;
|
||||
import org.eclipse.jdt.core.IPackageFragmentRoot;
|
||||
import org.eclipse.jdt.core.IType;
|
||||
import org.eclipse.jdt.core.ITypeHierarchy;
|
||||
import org.eclipse.jdt.core.JavaCore;
|
||||
import org.eclipse.jdt.core.JavaModelException;
|
||||
import org.eclipse.jface.action.IAction;
|
||||
import org.eclipse.jface.viewers.ISelection;
|
||||
import org.eclipse.jface.viewers.IStructuredSelection;
|
||||
import org.eclipse.ui.IObjectActionDelegate;
|
||||
import org.eclipse.ui.IWorkbenchPart;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Action going through all the source of a project and creating a pre-processed aidl file
|
||||
* with all the custom parcelable classes.
|
||||
*/
|
||||
public class CreateAidlImportAction implements IObjectActionDelegate {
|
||||
|
||||
private ISelection mSelection;
|
||||
|
||||
public CreateAidlImportAction() {
|
||||
// pass
|
||||
}
|
||||
|
||||
public void setActivePart(IAction action, IWorkbenchPart targetPart) {
|
||||
// pass
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.eclipse.ui.IActionDelegate#run(org.eclipse.jface.action.IAction)
|
||||
*/
|
||||
public void run(IAction action) {
|
||||
if (mSelection instanceof IStructuredSelection) {
|
||||
for (Iterator<?> it = ((IStructuredSelection)mSelection).iterator(); it.hasNext();) {
|
||||
Object element = it.next();
|
||||
IProject project = null;
|
||||
if (element instanceof IProject) {
|
||||
project = (IProject)element;
|
||||
} else if (element instanceof IAdaptable) {
|
||||
project = (IProject)((IAdaptable)element).getAdapter(IProject.class);
|
||||
}
|
||||
if (project != null) {
|
||||
final IProject fproject = project;
|
||||
new Job("Aidl preprocess") {
|
||||
@Override
|
||||
protected IStatus run(IProgressMonitor monitor) {
|
||||
return createImportFile(fproject, monitor);
|
||||
}
|
||||
}.schedule();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void selectionChanged(IAction action, ISelection selection) {
|
||||
mSelection = selection;
|
||||
}
|
||||
|
||||
private IStatus createImportFile(IProject project, IProgressMonitor monitor) {
|
||||
try {
|
||||
if (monitor != null) {
|
||||
monitor.beginTask(String.format(
|
||||
"Creating aid preprocess file for %1$s", project.getName()), 1);
|
||||
}
|
||||
|
||||
ArrayList<String> parcelables = new ArrayList<String>();
|
||||
|
||||
IJavaProject javaProject = JavaCore.create(project);
|
||||
|
||||
IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots();
|
||||
|
||||
for (IPackageFragmentRoot root : roots) {
|
||||
if (root.isArchive() == false && root.isExternal() == false) {
|
||||
parsePackageFragmentRoot(root, parcelables, monitor);
|
||||
}
|
||||
}
|
||||
|
||||
// create the file with the parcelables
|
||||
if (parcelables.size() > 0) {
|
||||
IPath path = project.getLocation();
|
||||
path = path.append(AndroidConstants.FN_PROJECT_AIDL);
|
||||
|
||||
File f = new File(path.toOSString());
|
||||
if (f.exists() == false) {
|
||||
if (f.createNewFile() == false) {
|
||||
return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
|
||||
"Failed to create /project.aidl");
|
||||
}
|
||||
}
|
||||
|
||||
FileWriter fw = new FileWriter(f);
|
||||
|
||||
fw.write("// This file is auto-generated by the\n");
|
||||
fw.write("// 'Create Aidl preprocess file for Parcelable classes'\n");
|
||||
fw.write("// action. Do not modify!\n\n");
|
||||
|
||||
for (String parcelable : parcelables) {
|
||||
fw.write("parcelable "); //$NON-NLS-1$
|
||||
fw.write(parcelable);
|
||||
fw.append(";\n"); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
fw.close();
|
||||
|
||||
// need to refresh the level just below the project to make sure it's being picked
|
||||
// up by eclipse.
|
||||
project.refreshLocal(IResource.DEPTH_ONE, monitor);
|
||||
}
|
||||
|
||||
if (monitor != null) {
|
||||
monitor.worked(1);
|
||||
monitor.done();
|
||||
}
|
||||
|
||||
return Status.OK_STATUS;
|
||||
} catch (JavaModelException e) {
|
||||
return e.getJavaModelStatus();
|
||||
} catch (IOException e) {
|
||||
return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
|
||||
"Failed to create /project.aidl", e);
|
||||
} catch (CoreException e) {
|
||||
return e.getStatus();
|
||||
} finally {
|
||||
if (monitor != null) {
|
||||
monitor.done();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parsePackageFragmentRoot(IPackageFragmentRoot root,
|
||||
ArrayList<String> parcelables, IProgressMonitor monitor) throws JavaModelException {
|
||||
|
||||
IJavaElement[] elements = root.getChildren();
|
||||
|
||||
for (IJavaElement element : elements) {
|
||||
if (element instanceof IPackageFragment) {
|
||||
ICompilationUnit[] compilationUnits =
|
||||
((IPackageFragment)element).getCompilationUnits();
|
||||
|
||||
for (ICompilationUnit unit : compilationUnits) {
|
||||
IType[] types = unit.getTypes();
|
||||
|
||||
for (IType type : types) {
|
||||
parseType(type, parcelables, monitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseType(IType type, ArrayList<String> parcelables, IProgressMonitor monitor)
|
||||
throws JavaModelException {
|
||||
// first look in this type if it somehow extends parcelable.
|
||||
ITypeHierarchy typeHierarchy = type.newSupertypeHierarchy(monitor);
|
||||
|
||||
IType[] superInterfaces = typeHierarchy.getAllSuperInterfaces(type);
|
||||
for (IType superInterface : superInterfaces) {
|
||||
if (AndroidConstants.CLASS_PARCELABLE.equals(superInterface.getFullyQualifiedName())) {
|
||||
parcelables.add(type.getFullyQualifiedName());
|
||||
}
|
||||
}
|
||||
|
||||
// then look in inner types.
|
||||
IType[] innerTypes = type.getTypes();
|
||||
|
||||
for (IType innerType : innerTypes) {
|
||||
parseType(innerType, parcelables, monitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
package com.android.ide.eclipse.adt.project;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.debug.launching.LaunchConfigDelegate;
|
||||
import com.android.ide.eclipse.adt.launch.LaunchConfigDelegate;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
|
||||
@@ -39,7 +39,7 @@ public class FolderDecorator implements ILightweightLabelDecorator {
|
||||
private ImageDescriptor mDescriptor;
|
||||
|
||||
public FolderDecorator() {
|
||||
mDescriptor = AdtPlugin.getImageDescriptor("/icons/android_project.png");
|
||||
mDescriptor = AdtPlugin.getImageDescriptor("/icons/android_project.png"); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
public void decorate(Object element, IDecoration decoration) {
|
||||
@@ -55,13 +55,13 @@ public class FolderDecorator implements ILightweightLabelDecorator {
|
||||
if (folder.getParent().getType() == IResource.PROJECT) {
|
||||
String name = folder.getName();
|
||||
if (name.equals(SdkConstants.FD_ASSETS)) {
|
||||
decorate(decoration, " [Android assets]");
|
||||
decoration.addOverlay(mDescriptor, IDecoration.TOP_RIGHT);
|
||||
doDecoration(decoration, null);
|
||||
} else if (name.equals(SdkConstants.FD_RESOURCES)) {
|
||||
decorate(decoration, " [Android resources]");
|
||||
decoration.addOverlay(mDescriptor, IDecoration.TOP_RIGHT);
|
||||
} else if (name.equals(SdkConstants.FD_NATIVE_LIBS)) {
|
||||
decorate(decoration, " [Native Libraries]");
|
||||
doDecoration(decoration, null);
|
||||
} else if (name.equals(SdkConstants.FD_GEN_SOURCES)) {
|
||||
doDecoration(decoration, " [Generated Java Files]");
|
||||
} else if (name.equals(SdkConstants.FD_NATIVE_LIBS)) {
|
||||
doDecoration(decoration, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,20 +72,24 @@ public class FolderDecorator implements ILightweightLabelDecorator {
|
||||
}
|
||||
}
|
||||
|
||||
public void decorate(IDecoration decoration, String suffix) {
|
||||
decoration.addOverlay(mDescriptor, IDecoration.TOP_RIGHT);
|
||||
public void doDecoration(IDecoration decoration, String suffix) {
|
||||
decoration.addOverlay(mDescriptor, IDecoration.TOP_LEFT);
|
||||
|
||||
// this is broken as it changes the color of the whole text, not only of the decoration.
|
||||
// TODO: figure out how to change the color of the decoration only.
|
||||
// decoration.addSuffix(suffix);
|
||||
// ITheme theme = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme();
|
||||
// ColorRegistry registry = theme.getColorRegistry();
|
||||
// decoration.setForegroundColor(registry.get("org.eclipse.jdt.ui.ColoredLabels.decorations"));
|
||||
if (suffix != null) {
|
||||
decoration.addSuffix(suffix);
|
||||
|
||||
// this is broken as it changes the color of the whole text, not only of the decoration.
|
||||
// TODO: figure out how to change the color of the decoration only.
|
||||
// ITheme theme = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme();
|
||||
// ColorRegistry registry = theme.getColorRegistry();
|
||||
// decoration.setForegroundColor(
|
||||
// registry.get("org.eclipse.jdt.ui.ColoredLabels.decorations")); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public boolean isLabelProperty(Object element, String property) {
|
||||
// at this time return false.
|
||||
// Property change do not affect the label
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -93,13 +97,11 @@ public class FolderDecorator implements ILightweightLabelDecorator {
|
||||
// No state change will affect the rendering.
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void removeListener(ILabelProviderListener listener) {
|
||||
// No state change will affect the rendering.
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
// nothind to dispose
|
||||
// nothing to dispose
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@ package com.android.ide.eclipse.adt.project;
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.project.internal.AndroidClasspathContainerInitializer;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.common.project.AndroidManifestHelper;
|
||||
import com.android.ide.eclipse.common.project.AndroidManifestParser;
|
||||
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.resources.IMarker;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IProjectDescription;
|
||||
@@ -361,7 +361,7 @@ public final class ProjectHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link IProject} by its running application name, as it returned by the VM.
|
||||
* Returns a {@link IProject} by its running application name, as it returned by the AVD.
|
||||
* <p/>
|
||||
* <var>applicationName</var> will in most case be the package declared in the manifest, but
|
||||
* can, in some cases, be a custom process name declared in the manifest, in the
|
||||
@@ -389,19 +389,20 @@ public final class ProjectHelper {
|
||||
continue;
|
||||
}
|
||||
|
||||
AndroidManifestHelper androidManifest = new AndroidManifestHelper(p);
|
||||
|
||||
// check that there is indeed a manifest file.
|
||||
if (androidManifest.getManifestIFile() == null) {
|
||||
IFile manifestFile = AndroidManifestParser.getManifest(p);
|
||||
if (manifestFile == null) {
|
||||
// no file? skip this project.
|
||||
continue;
|
||||
}
|
||||
|
||||
AndroidManifestParser parser = null;
|
||||
try {
|
||||
parser = AndroidManifestParser.parseForData(
|
||||
androidManifest.getManifestIFile());
|
||||
parser = AndroidManifestParser.parseForData(manifestFile);
|
||||
} catch (CoreException e) {
|
||||
// ignore, handled below.
|
||||
}
|
||||
if (parser == null) {
|
||||
// skip this project.
|
||||
continue;
|
||||
}
|
||||
@@ -665,4 +666,17 @@ public final class ProjectHelper {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the apk filename for the given project
|
||||
* @param project The project.
|
||||
* @param config An optional config name. Can be null.
|
||||
*/
|
||||
public static String getApkFilename(IProject project, String config) {
|
||||
if (config != null) {
|
||||
return project.getName() + "-" + config + AndroidConstants.DOT_ANDROID_PACKAGE; //$NON-NLS-1$
|
||||
}
|
||||
|
||||
return project.getName() + AndroidConstants.DOT_ANDROID_PACKAGE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,14 +18,20 @@ package com.android.ide.eclipse.adt.project.export;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.project.ProjectHelper;
|
||||
import com.android.ide.eclipse.common.project.BaseProjectHelper;
|
||||
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.IFolder;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
import org.eclipse.core.resources.IncrementalProjectBuilder;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IAdaptable;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.jface.operation.IRunnableWithProgress;
|
||||
import org.eclipse.jface.resource.ImageDescriptor;
|
||||
import org.eclipse.jface.viewers.IStructuredSelection;
|
||||
import org.eclipse.jface.wizard.Wizard;
|
||||
@@ -35,12 +41,14 @@ import org.eclipse.swt.events.VerifyListener;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
import org.eclipse.ui.IExportWizard;
|
||||
import org.eclipse.ui.IWorkbench;
|
||||
import org.eclipse.ui.PlatformUI;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@@ -49,6 +57,9 @@ import java.security.KeyStore.PrivateKeyEntry;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* Export wizard to export an apk signed with a release key/certificate.
|
||||
@@ -66,7 +77,12 @@ public final class ExportWizard extends Wizard implements IExportWizard {
|
||||
static final String PROPERTY_KEYSTORE = "keystore"; //$NON-NLS-1$
|
||||
static final String PROPERTY_ALIAS = "alias"; //$NON-NLS-1$
|
||||
static final String PROPERTY_DESTINATION = "destination"; //$NON-NLS-1$
|
||||
static final String PROPERTY_FILENAME = "baseFilename"; //$NON-NLS-1$
|
||||
|
||||
static final int APK_FILE_SOURCE = 0;
|
||||
static final int APK_FILE_DEST = 1;
|
||||
static final int APK_COUNT = 2;
|
||||
|
||||
/**
|
||||
* Base page class for the ExportWizard page. This class add the {@link #onShow()} callback.
|
||||
*/
|
||||
@@ -131,7 +147,7 @@ public final class ExportWizard extends Wizard implements IExportWizard {
|
||||
* Calls {@link #setErrorMessage(String)} and {@link #setPageComplete(boolean)} based on a
|
||||
* {@link Throwable} object.
|
||||
*/
|
||||
protected final void onException(Throwable t) {
|
||||
protected void onException(Throwable t) {
|
||||
String message = getExceptionMessage(t);
|
||||
|
||||
setErrorMessage(message);
|
||||
@@ -155,9 +171,7 @@ public final class ExportWizard extends Wizard implements IExportWizard {
|
||||
private PrivateKey mPrivateKey;
|
||||
private X509Certificate mCertificate;
|
||||
|
||||
private String mDestinationPath;
|
||||
private String mApkFilePath;
|
||||
private String mApkFileName;
|
||||
private File mDestinationParentFolder;
|
||||
|
||||
private ExportWizardPage mKeystoreSelectionPage;
|
||||
private ExportWizardPage mKeyCreationPage;
|
||||
@@ -168,6 +182,8 @@ public final class ExportWizard extends Wizard implements IExportWizard {
|
||||
|
||||
private List<String> mExistingAliases;
|
||||
|
||||
private Map<String, String[]> mApkMap;
|
||||
|
||||
public ExportWizard() {
|
||||
setHelpAvailable(false); // TODO have help
|
||||
setWindowTitle("Export Android Application");
|
||||
@@ -186,24 +202,51 @@ public final class ExportWizard extends Wizard implements IExportWizard {
|
||||
|
||||
@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);
|
||||
ProjectHelper.saveStringProperty(mProject, PROPERTY_DESTINATION,
|
||||
mDestinationParentFolder.getAbsolutePath());
|
||||
ProjectHelper.saveStringProperty(mProject, PROPERTY_FILENAME,
|
||||
mApkMap.get(null)[APK_FILE_DEST]);
|
||||
|
||||
// run the export in an UI runnable.
|
||||
IWorkbench workbench = PlatformUI.getWorkbench();
|
||||
final boolean[] result = new boolean[1];
|
||||
try {
|
||||
workbench.getProgressService().busyCursorWhile(new IRunnableWithProgress() {
|
||||
/**
|
||||
* Run the export.
|
||||
* @throws InvocationTargetException
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
public void run(IProgressMonitor monitor) throws InvocationTargetException,
|
||||
InterruptedException {
|
||||
try {
|
||||
result[0] = doExport(monitor);
|
||||
} finally {
|
||||
monitor.done();
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (InvocationTargetException e) {
|
||||
return false;
|
||||
} catch (InterruptedException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return result[0];
|
||||
}
|
||||
|
||||
private boolean doExport(IProgressMonitor monitor) {
|
||||
try {
|
||||
// first we make sure the project is built
|
||||
mProject.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor);
|
||||
|
||||
// if needed, create the keystore and/or key.
|
||||
if (mKeystoreCreationMode || mKeyCreationMode) {
|
||||
final ArrayList<String> output = new ArrayList<String>();
|
||||
if (KeystoreHelper.createNewStore(
|
||||
boolean createdStore = KeystoreHelper.createNewStore(
|
||||
mKeystore,
|
||||
null /*storeType*/,
|
||||
mKeystorePassword,
|
||||
@@ -218,7 +261,9 @@ public final class ExportWizard extends Wizard implements IExportWizard {
|
||||
public void out(String message) {
|
||||
output.add(message);
|
||||
}
|
||||
}) == false) {
|
||||
});
|
||||
|
||||
if (createdStore == false) {
|
||||
// keystore creation error!
|
||||
displayError(output.toArray(new String[output.size()]));
|
||||
return false;
|
||||
@@ -245,20 +290,42 @@ public final class ExportWizard extends Wizard implements IExportWizard {
|
||||
|
||||
// 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();
|
||||
// get the output folder of the project to export.
|
||||
// this is where we'll find the built apks to resign and export.
|
||||
IFolder outputIFolder = BaseProjectHelper.getOutputFolder(mProject);
|
||||
if (outputIFolder == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
builder.close();
|
||||
fos.close();
|
||||
String outputOsPath = outputIFolder.getLocation().toOSString();
|
||||
|
||||
// now generate the packages.
|
||||
Set<Entry<String, String[]>> set = mApkMap.entrySet();
|
||||
for (Entry<String, String[]> entry : set) {
|
||||
String[] defaultApk = entry.getValue();
|
||||
String srcFilename = defaultApk[APK_FILE_SOURCE];
|
||||
String destFilename = defaultApk[APK_FILE_DEST];
|
||||
|
||||
FileOutputStream fos = new FileOutputStream(
|
||||
new File(mDestinationParentFolder, destFilename));
|
||||
SignedJarBuilder builder = new SignedJarBuilder(fos, mPrivateKey, mCertificate);
|
||||
|
||||
// get the input file.
|
||||
FileInputStream fis = new FileInputStream(new File(outputOsPath, srcFilename));
|
||||
|
||||
// add the content of the source file to the output file, and sign it at
|
||||
// the same time.
|
||||
try {
|
||||
builder.writeZip(fis, null /* filter */);
|
||||
// close the builder: write the final signature files, and close the archive.
|
||||
builder.close();
|
||||
} finally {
|
||||
try {
|
||||
fis.close();
|
||||
} finally {
|
||||
fos.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
@@ -271,6 +338,8 @@ public final class ExportWizard extends Wizard implements IExportWizard {
|
||||
displayError(e);
|
||||
} catch (KeytoolException e) {
|
||||
displayError(e);
|
||||
} catch (CoreException e) {
|
||||
displayError(e);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -282,10 +351,10 @@ public final class ExportWizard extends Wizard implements IExportWizard {
|
||||
// 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 &&
|
||||
return mApkMap != null && mApkMap.size() > 0 &&
|
||||
((mPrivateKey != null && mCertificate != null)
|
||||
|| mKeystoreCreationMode || mKeyCreationMode) &&
|
||||
mDestinationPath != null;
|
||||
mDestinationParentFolder != null;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -334,18 +403,12 @@ public final class ExportWizard extends Wizard implements IExportWizard {
|
||||
return mProject;
|
||||
}
|
||||
|
||||
void setProject(IProject project, String apkFilePath, String filename) {
|
||||
void setProject(IProject project) {
|
||||
mProject = project;
|
||||
mApkFilePath = apkFilePath;
|
||||
mApkFileName = filename;
|
||||
|
||||
updatePageOnChange(ExportWizardPage.DATA_PROJECT);
|
||||
}
|
||||
|
||||
String getApkFilename() {
|
||||
return mApkFileName;
|
||||
}
|
||||
|
||||
void setKeystore(String path) {
|
||||
mKeystore = path;
|
||||
mPrivateKey = null;
|
||||
@@ -444,10 +507,16 @@ public final class ExportWizard extends Wizard implements IExportWizard {
|
||||
mCertificate = certificate;
|
||||
}
|
||||
|
||||
void setDestination(String path) {
|
||||
mDestinationPath = path;
|
||||
void setDestination(File parentFolder, Map<String, String[]> apkMap) {
|
||||
mDestinationParentFolder = parentFolder;
|
||||
mApkMap = apkMap;
|
||||
}
|
||||
|
||||
|
||||
void resetDestination() {
|
||||
mDestinationParentFolder = null;
|
||||
mApkMap = null;
|
||||
}
|
||||
|
||||
void updatePageOnChange(int changeMask) {
|
||||
for (ExportWizardPage page : mPages) {
|
||||
page.projectDataChanged(changeMask);
|
||||
@@ -484,7 +553,7 @@ public final class ExportWizard extends Wizard implements IExportWizard {
|
||||
* <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) {
|
||||
static String getExceptionMessage(Throwable t) {
|
||||
String message = t.getMessage();
|
||||
if (message == null) {
|
||||
Throwable cause = t.getCause();
|
||||
|
||||
@@ -18,13 +18,18 @@ 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 com.android.ide.eclipse.adt.sdk.Sdk;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.custom.ScrolledComposite;
|
||||
import org.eclipse.swt.events.ControlAdapter;
|
||||
import org.eclipse.swt.events.ControlEvent;
|
||||
import org.eclipse.swt.events.ModifyEvent;
|
||||
import org.eclipse.swt.events.ModifyListener;
|
||||
import org.eclipse.swt.events.SelectionAdapter;
|
||||
import org.eclipse.swt.events.SelectionEvent;
|
||||
import org.eclipse.swt.graphics.Rectangle;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Button;
|
||||
@@ -47,6 +52,10 @@ import java.security.KeyStore.PrivateKeyEntry;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* Final page of the wizard that checks the key and ask for the ouput location.
|
||||
@@ -59,6 +68,12 @@ final class KeyCheckPage extends ExportWizardPage {
|
||||
private Text mDestination;
|
||||
private boolean mFatalSigningError;
|
||||
private FormText mDetailText;
|
||||
/** The Apk Config map for the current project */
|
||||
private Map<String, String> mApkConfig;
|
||||
private ScrolledComposite mScrolledComposite;
|
||||
|
||||
private String mKeyDetails;
|
||||
private String mDestinationDetails;
|
||||
|
||||
protected KeyCheckPage(ExportWizard wizard, String pageName) {
|
||||
super(pageName);
|
||||
@@ -86,7 +101,7 @@ final class KeyCheckPage extends ExportWizardPage {
|
||||
mDestination.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
|
||||
mDestination.addModifyListener(new ModifyListener() {
|
||||
public void modifyText(ModifyEvent e) {
|
||||
onDestinationChange();
|
||||
onDestinationChange(false /*forceDetailUpdate*/);
|
||||
}
|
||||
});
|
||||
final Button browseButton = new Button(composite, SWT.PUSH);
|
||||
@@ -97,7 +112,10 @@ final class KeyCheckPage extends ExportWizardPage {
|
||||
FileDialog fileDialog = new FileDialog(browseButton.getShell(), SWT.SAVE);
|
||||
|
||||
fileDialog.setText("Destination file name");
|
||||
fileDialog.setFileName(mWizard.getApkFilename());
|
||||
// get a default apk name based on the project
|
||||
String filename = ProjectHelper.getApkFilename(mWizard.getProject(),
|
||||
null /*config*/);
|
||||
fileDialog.setFileName(filename);
|
||||
|
||||
String saveLocation = fileDialog.open();
|
||||
if (saveLocation != null) {
|
||||
@@ -106,9 +124,21 @@ final class KeyCheckPage extends ExportWizardPage {
|
||||
}
|
||||
});
|
||||
|
||||
mDetailText = new FormText(composite, SWT.NONE);
|
||||
mDetailText.setLayoutData(gd = new GridData(GridData.FILL_BOTH));
|
||||
mScrolledComposite = new ScrolledComposite(composite, SWT.V_SCROLL);
|
||||
mScrolledComposite.setLayoutData(gd = new GridData(GridData.FILL_BOTH));
|
||||
gd.horizontalSpan = 3;
|
||||
mScrolledComposite.setExpandHorizontal(true);
|
||||
mScrolledComposite.setExpandVertical(true);
|
||||
|
||||
mDetailText = new FormText(mScrolledComposite, SWT.NONE);
|
||||
mScrolledComposite.setContent(mDetailText);
|
||||
|
||||
mScrolledComposite.addControlListener(new ControlAdapter() {
|
||||
@Override
|
||||
public void controlResized(ControlEvent e) {
|
||||
updateScrolling();
|
||||
}
|
||||
});
|
||||
|
||||
setControl(composite);
|
||||
}
|
||||
@@ -119,11 +149,14 @@ final class KeyCheckPage extends ExportWizardPage {
|
||||
if ((mProjectDataChanged & DATA_PROJECT) != 0) {
|
||||
// reset the destination from the content of the project
|
||||
IProject project = mWizard.getProject();
|
||||
mApkConfig = Sdk.getCurrent().getProjectApkConfigs(project);
|
||||
|
||||
String destination = ProjectHelper.loadStringProperty(project,
|
||||
ExportWizard.PROPERTY_DESTINATION);
|
||||
if (destination != null) {
|
||||
mDestination.setText(destination);
|
||||
String filename = ProjectHelper.loadStringProperty(project,
|
||||
ExportWizard.PROPERTY_FILENAME);
|
||||
if (destination != null && filename != null) {
|
||||
mDestination.setText(destination + File.separator + filename);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,11 +167,14 @@ final class KeyCheckPage extends ExportWizardPage {
|
||||
// reset the wizard with no key/cert to make it not finishable, unless a valid
|
||||
// key/cert is found.
|
||||
mWizard.setSigningInfo(null, null);
|
||||
mPrivateKey = null;
|
||||
mCertificate = null;
|
||||
mKeyDetails = null;
|
||||
|
||||
if (mWizard.getKeystoreCreationMode() || mWizard.getKeyCreationMode()) {
|
||||
int validity = mWizard.getValidity();
|
||||
StringBuilder sb = new StringBuilder(
|
||||
String.format("<form><p>Certificate expires in %d years.</p>",
|
||||
String.format("<p>Certificate expires in %d years.</p>",
|
||||
validity));
|
||||
|
||||
if (validity < 25) {
|
||||
@@ -149,8 +185,7 @@ final class KeyCheckPage extends ExportWizardPage {
|
||||
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 */);
|
||||
mKeyDetails = sb.toString();
|
||||
} else {
|
||||
try {
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
@@ -192,10 +227,9 @@ final class KeyCheckPage extends ExportWizardPage {
|
||||
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 */);
|
||||
mKeyDetails = String.format(
|
||||
"<p>Certificate expired on %s</p>",
|
||||
mCertificate.getNotAfter().toString());
|
||||
|
||||
// fatal error = nothing can make the page complete.
|
||||
mFatalSigningError = true;
|
||||
@@ -207,7 +241,7 @@ final class KeyCheckPage extends ExportWizardPage {
|
||||
mWizard.setSigningInfo(mPrivateKey, mCertificate);
|
||||
|
||||
StringBuilder sb = new StringBuilder(String.format(
|
||||
"<form><p>Certificate expires on %s.</p>",
|
||||
"<p>Certificate expires on %s.</p>",
|
||||
mCertificate.getNotAfter().toString()));
|
||||
|
||||
int expirationYear = expirationCalendar.get(Calendar.YEAR);
|
||||
@@ -232,11 +266,8 @@ final class KeyCheckPage extends ExportWizardPage {
|
||||
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 */);
|
||||
mKeyDetails = sb.toString();
|
||||
}
|
||||
mDetailText.getParent().layout();
|
||||
} else {
|
||||
// fatal error = nothing can make the page complete.
|
||||
mFatalSigningError = true;
|
||||
@@ -244,10 +275,15 @@ final class KeyCheckPage extends ExportWizardPage {
|
||||
}
|
||||
}
|
||||
|
||||
onDestinationChange();
|
||||
onDestinationChange(true /*forceDetailUpdate*/);
|
||||
}
|
||||
|
||||
private void onDestinationChange() {
|
||||
/**
|
||||
* Callback for destination field edition
|
||||
* @param forceDetailUpdate if true, the detail {@link FormText} is updated even if a fatal
|
||||
* error has happened in the signing.
|
||||
*/
|
||||
private void onDestinationChange(boolean forceDetailUpdate) {
|
||||
if (mFatalSigningError == false) {
|
||||
// reset messages for now.
|
||||
setErrorMessage(null);
|
||||
@@ -257,7 +293,8 @@ final class KeyCheckPage extends ExportWizardPage {
|
||||
|
||||
if (path.length() == 0) {
|
||||
setErrorMessage("Enter destination for the APK file.");
|
||||
mWizard.setDestination(null); // this is to reset canFinish in the wizard
|
||||
// reset canFinish in the wizard.
|
||||
mWizard.resetDestination();
|
||||
setPageComplete(false);
|
||||
return;
|
||||
}
|
||||
@@ -265,27 +302,142 @@ final class KeyCheckPage extends ExportWizardPage {
|
||||
File file = new File(path);
|
||||
if (file.isDirectory()) {
|
||||
setErrorMessage("Destination is a directory.");
|
||||
mWizard.setDestination(null); // this is to reset canFinish in the wizard
|
||||
// reset canFinish in the wizard.
|
||||
mWizard.resetDestination();
|
||||
setPageComplete(false);
|
||||
return;
|
||||
}
|
||||
|
||||
File parentFile = file.getParentFile();
|
||||
if (parentFile == null || parentFile.isDirectory() == false) {
|
||||
File parentFolder = file.getParentFile();
|
||||
if (parentFolder == null || parentFolder.isDirectory() == false) {
|
||||
setErrorMessage("Not a valid directory.");
|
||||
mWizard.setDestination(null); // this is to reset canFinish in the wizard
|
||||
// reset canFinish in the wizard.
|
||||
mWizard.resetDestination();
|
||||
setPageComplete(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// display the list of files that will actually be created
|
||||
Map<String, String[]> apkFileMap = getApkFileMap(file);
|
||||
|
||||
// display them
|
||||
boolean fileExists = false;
|
||||
StringBuilder sb = new StringBuilder(String.format(
|
||||
"<p>This will create the following files:</p>"));
|
||||
|
||||
Set<Entry<String, String[]>> set = apkFileMap.entrySet();
|
||||
for (Entry<String, String[]> entry : set) {
|
||||
String[] apkArray = entry.getValue();
|
||||
String filename = apkArray[ExportWizard.APK_FILE_DEST];
|
||||
File f = new File(parentFolder, filename);
|
||||
if (f.isFile()) {
|
||||
fileExists = true;
|
||||
sb.append(String.format("<li>%1$s (WARNING: already exists)</li>", filename));
|
||||
} else if (f.isDirectory()) {
|
||||
setErrorMessage(String.format("%1$s is a directory.", filename));
|
||||
// reset canFinish in the wizard.
|
||||
mWizard.resetDestination();
|
||||
setPageComplete(false);
|
||||
return;
|
||||
} else {
|
||||
sb.append(String.format("<li>%1$s</li>", filename));
|
||||
}
|
||||
}
|
||||
|
||||
mDestinationDetails = sb.toString();
|
||||
|
||||
// no error, set the destination in the wizard.
|
||||
mWizard.setDestination(path);
|
||||
mWizard.setDestination(parentFolder, apkFileMap);
|
||||
setPageComplete(true);
|
||||
|
||||
|
||||
// However, we should also test if the file already exists.
|
||||
if (file.isFile()) {
|
||||
setMessage("Destination file already exists.", WARNING);
|
||||
if (fileExists) {
|
||||
setMessage("A destination file already exists.", WARNING);
|
||||
}
|
||||
|
||||
updateDetailText();
|
||||
} else if (forceDetailUpdate) {
|
||||
updateDetailText();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the scrollbar to match the content of the {@link FormText} or the new size
|
||||
* of the {@link ScrolledComposite}.
|
||||
*/
|
||||
private void updateScrolling() {
|
||||
if (mDetailText != null) {
|
||||
Rectangle r = mScrolledComposite.getClientArea();
|
||||
mScrolledComposite.setMinSize(mDetailText.computeSize(r.width, SWT.DEFAULT));
|
||||
mScrolledComposite.layout();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDetailText() {
|
||||
StringBuilder sb = new StringBuilder("<form>");
|
||||
if (mKeyDetails != null) {
|
||||
sb.append(mKeyDetails);
|
||||
}
|
||||
|
||||
if (mDestinationDetails != null && mFatalSigningError == false) {
|
||||
sb.append(mDestinationDetails);
|
||||
}
|
||||
|
||||
sb.append("</form>");
|
||||
|
||||
mDetailText.setText(sb.toString(), true /* parseTags */,
|
||||
true /* expandURLs */);
|
||||
|
||||
mDetailText.getParent().layout();
|
||||
|
||||
updateScrolling();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the list of destination filenames based on the content of the destination field
|
||||
* and the list of APK configurations for the project.
|
||||
*
|
||||
* @param file File name from the destination field
|
||||
* @return A list of destination filenames based <code>file</code> and the list of APK
|
||||
* configurations for the project.
|
||||
*/
|
||||
private Map<String, String[]> getApkFileMap(File file) {
|
||||
String filename = file.getName();
|
||||
|
||||
HashMap<String, String[]> map = new HashMap<String, String[]>();
|
||||
|
||||
// add the default APK filename
|
||||
String[] apkArray = new String[ExportWizard.APK_COUNT];
|
||||
apkArray[ExportWizard.APK_FILE_SOURCE] = ProjectHelper.getApkFilename(
|
||||
mWizard.getProject(), null /*config*/);
|
||||
apkArray[ExportWizard.APK_FILE_DEST] = filename;
|
||||
map.put(null, apkArray);
|
||||
|
||||
// add the APKs for each APK configuration.
|
||||
if (mApkConfig != null && mApkConfig.size() > 0) {
|
||||
// remove the extension.
|
||||
int index = filename.lastIndexOf('.');
|
||||
String base = filename.substring(0, index);
|
||||
String extension = filename.substring(index);
|
||||
|
||||
Set<Entry<String, String>> set = mApkConfig.entrySet();
|
||||
for (Entry<String, String> entry : set) {
|
||||
apkArray = new String[ExportWizard.APK_COUNT];
|
||||
apkArray[ExportWizard.APK_FILE_SOURCE] = ProjectHelper.getApkFilename(
|
||||
mWizard.getProject(), entry.getKey());
|
||||
apkArray[ExportWizard.APK_FILE_DEST] = base + "-" + entry.getKey() + extension;
|
||||
map.put(entry.getKey(), apkArray);
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onException(Throwable t) {
|
||||
super.onException(t);
|
||||
|
||||
mKeyDetails = String.format("ERROR: %1$s", ExportWizard.getExceptionMessage(t));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +266,7 @@ final class ProjectCheckPage extends ExportWizardPage {
|
||||
}
|
||||
|
||||
// update the wizard with the new project
|
||||
mWizard.setProject(null, null, null);
|
||||
mWizard.setProject(null);
|
||||
|
||||
//test the project name first!
|
||||
String text = mProjectText.getText().trim();
|
||||
@@ -289,7 +289,7 @@ final class ProjectCheckPage extends ExportWizardPage {
|
||||
setErrorMessage(null);
|
||||
|
||||
// update the wizard with the new project
|
||||
setApkFilePathInWizard(found);
|
||||
mWizard.setProject(found);
|
||||
|
||||
// now rebuild the error ui.
|
||||
buildErrorUi(found);
|
||||
@@ -299,24 +299,4 @@ final class ProjectCheckPage extends ExportWizardPage {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setApkFilePathInWizard(IProject project) {
|
||||
if (project != null) {
|
||||
IFolder outputIFolder = BaseProjectHelper.getOutputFolder(project);
|
||||
if (outputIFolder != null) {
|
||||
String outputOsPath = outputIFolder.getLocation().toOSString();
|
||||
String apkFilePath = outputOsPath + File.separator + project.getName() +
|
||||
AndroidConstants.DOT_ANDROID_PACKAGE;
|
||||
|
||||
File f = new File(apkFilePath);
|
||||
if (f.isFile()) {
|
||||
mWizard.setProject(project, apkFilePath, f.getName());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mWizard.setProject(null, null, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ 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;
|
||||
@@ -44,8 +45,12 @@ import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jdt.core.JavaCore;
|
||||
import org.eclipse.jdt.core.JavaModelException;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Classpath container initializer responsible for binding {@link AndroidClasspathContainer} to
|
||||
@@ -56,6 +61,21 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit
|
||||
private final static String CONTAINER_ID =
|
||||
"com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"; //$NON-NLS-1$
|
||||
|
||||
/** path separator to store multiple paths in a single property. This is guaranteed to not
|
||||
* be in a path.
|
||||
*/
|
||||
private final static String PATH_SEPARATOR = "\u001C"; //$NON-NLS-1$
|
||||
|
||||
private final static String PROPERTY_CONTAINER_CACHE = "androidContainerCache"; //$NON-NLS-1$
|
||||
private final static String PROPERTY_TARGET_NAME = "androidTargetCache"; //$NON-NLS-1$
|
||||
private final static String CACHE_VERSION = "01"; //$NON-NLS-1$
|
||||
private final static String CACHE_VERSION_SEP = CACHE_VERSION + PATH_SEPARATOR;
|
||||
|
||||
private final static int PATH_ANDROID_JAR = 0;
|
||||
private final static int PATH_ANDROID_SRC = 1;
|
||||
private final static int PATH_ANDROID_DOCS = 2;
|
||||
private final static int PATH_ANDROID_OPT_DOCS = 3;
|
||||
|
||||
public AndroidClasspathContainerInitializer() {
|
||||
// pass
|
||||
}
|
||||
@@ -71,7 +91,7 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit
|
||||
if (CONTAINER_ID.equals(containerPath.toString())) {
|
||||
JavaCore.setClasspathContainer(new Path(CONTAINER_ID),
|
||||
new IJavaProject[] { project },
|
||||
new IClasspathContainer[] { allocateAndroidContainer(CONTAINER_ID, project) },
|
||||
new IClasspathContainer[] { allocateAndroidContainer(project) },
|
||||
new NullProgressMonitor());
|
||||
}
|
||||
}
|
||||
@@ -111,7 +131,7 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit
|
||||
|
||||
IClasspathContainer[] containers = new IClasspathContainer[projectCount];
|
||||
for (int i = 0 ; i < projectCount; i++) {
|
||||
containers[i] = allocateAndroidContainer(CONTAINER_ID, androidProjects[i]);
|
||||
containers[i] = allocateAndroidContainer(androidProjects[i]);
|
||||
}
|
||||
|
||||
// give each project their new container in one call.
|
||||
@@ -128,133 +148,171 @@ 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,
|
||||
IJavaProject javaProject) {
|
||||
private static IClasspathContainer allocateAndroidContainer(IJavaProject javaProject) {
|
||||
final 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.
|
||||
// By passing the javaProject to getSdkLoadStatus(), we ensure that, should the SDK
|
||||
// not be loaded yet, the classpath container will be resolved again once the SDK is 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;
|
||||
String markerMessage = null;
|
||||
boolean outputToConsole = true;
|
||||
if (hashString == null || hashString.length() == 0) {
|
||||
// if there is no hash string we only show this if the SDK is loaded.
|
||||
// For a project opened at start-up with no target, this would be displayed twice,
|
||||
// once when the project is opened, and once after the SDK has finished loading.
|
||||
// By testing the sdk is loaded, we only show this once in the console.
|
||||
if (sdkIsLoaded) {
|
||||
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;
|
||||
}
|
||||
|
||||
if (message != null) {
|
||||
// log the error and put the marker on the project if we can.
|
||||
if (outputToConsole) {
|
||||
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_ALWAYS, iProject, message);
|
||||
}
|
||||
try {
|
||||
AdtPlugin plugin = AdtPlugin.getDefault();
|
||||
|
||||
try {
|
||||
BaseProjectHelper.addMarker(iProject, AdtConstants.MARKER_TARGET, message, -1,
|
||||
IMarker.SEVERITY_ERROR, IMarker.PRIORITY_HIGH);
|
||||
} catch (CoreException e) {
|
||||
// In some cases, the workspace may be locked for modification when we pass here.
|
||||
// We schedule a new job to put the marker after.
|
||||
final String fmessage = message;
|
||||
Job markerJob = new Job("Android SDK: Resolving error markers") {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected IStatus run(IProgressMonitor monitor) {
|
||||
try {
|
||||
BaseProjectHelper.addMarker(iProject, AdtConstants.MARKER_TARGET,
|
||||
fmessage, -1, IMarker.SEVERITY_ERROR, IMarker.PRIORITY_HIGH);
|
||||
} catch (CoreException e2) {
|
||||
return e2.getStatus();
|
||||
}
|
||||
// get the lock object for project manipulation during SDK load.
|
||||
Object lock = plugin.getSdkLockObject();
|
||||
synchronized (lock) {
|
||||
boolean sdkIsLoaded = plugin.getSdkLoadStatus() == LoadStatus.LOADED;
|
||||
|
||||
// check if the project has a valid target.
|
||||
IAndroidTarget target = null;
|
||||
if (sdkIsLoaded) {
|
||||
target = Sdk.getCurrent().getTarget(iProject);
|
||||
}
|
||||
|
||||
return Status.OK_STATUS;
|
||||
// if we are loaded and the target is non null, we create a valid ClassPathContainer
|
||||
if (sdkIsLoaded && target != null) {
|
||||
String targetName = target.getFullName();
|
||||
|
||||
return new AndroidClasspathContainer(
|
||||
createClasspathEntries(iProject, target, targetName),
|
||||
new Path(CONTAINER_ID), targetName);
|
||||
}
|
||||
|
||||
// In case of error, we'll try different thing to provide the best error message
|
||||
// possible.
|
||||
// Get the project's target's hash string (if it exists)
|
||||
String hashString = Sdk.getProjectTargetHashString(iProject);
|
||||
|
||||
if (hashString == null || hashString.length() == 0) {
|
||||
// if there is no hash string we only show this if the SDK is loaded.
|
||||
// For a project opened at start-up with no target, this would be displayed
|
||||
// twice, once when the project is opened, and once after the SDK has
|
||||
// finished loading.
|
||||
// By testing the sdk is loaded, we only show this once in the console.
|
||||
if (sdkIsLoaded) {
|
||||
markerMessage = String.format(
|
||||
"Project has no target set. Edit the project properties to set one.");
|
||||
}
|
||||
} else if (sdkIsLoaded) {
|
||||
markerMessage = String.format(
|
||||
"Unable to resolve target '%s'", hashString);
|
||||
} else {
|
||||
// this is the case where there is a hashString but the SDK is not yet
|
||||
// loaded and therefore we can't get the target yet.
|
||||
// We check if there is a cache of the needed information.
|
||||
AndroidClasspathContainer container = getContainerFromCache(iProject);
|
||||
|
||||
if (container == null) {
|
||||
// either the cache was wrong (ie folder does not exists anymore), or
|
||||
// there was no cache. In this case we need to make sure the project
|
||||
// is resolved again after the SDK is loaded.
|
||||
plugin.setProjectToResolve(javaProject);
|
||||
|
||||
markerMessage = String.format(
|
||||
"Unable to resolve target '%s' until the SDK is loaded.",
|
||||
hashString);
|
||||
|
||||
// let's not log this one to the console as it will happen at every boot,
|
||||
// and it's expected. (we do keep the error marker though).
|
||||
outputToConsole = false;
|
||||
|
||||
} else {
|
||||
// we created a container from the cache, so we register the project
|
||||
// to be checked for cache validity once the SDK is loaded
|
||||
plugin.setProjectToCheck(javaProject);
|
||||
|
||||
// and return the container
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// return a dummy container to replace the one we may have had before.
|
||||
// It'll be replaced by the real when if/when the target is resolved if/when the
|
||||
// SDK finishes loading.
|
||||
return new IClasspathContainer() {
|
||||
public IClasspathEntry[] getClasspathEntries() {
|
||||
return new IClasspathEntry[0];
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return "Unable to get system library for the project";
|
||||
}
|
||||
|
||||
public int getKind() {
|
||||
return IClasspathContainer.K_DEFAULT_SYSTEM;
|
||||
}
|
||||
|
||||
public IPath getPath() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
} finally {
|
||||
if (markerMessage != null) {
|
||||
// log the error and put the marker on the project if we can.
|
||||
if (outputToConsole) {
|
||||
AdtPlugin.printErrorToConsole(iProject, markerMessage);
|
||||
}
|
||||
|
||||
try {
|
||||
BaseProjectHelper.addMarker(iProject, AdtConstants.MARKER_TARGET, markerMessage,
|
||||
-1, IMarker.SEVERITY_ERROR, IMarker.PRIORITY_HIGH);
|
||||
} catch (CoreException e) {
|
||||
// In some cases, the workspace may be locked for modification when we
|
||||
// pass here.
|
||||
// We schedule a new job to put the marker after.
|
||||
final String fmessage = markerMessage;
|
||||
Job markerJob = new Job("Android SDK: Resolving error markers") {
|
||||
@Override
|
||||
protected IStatus run(IProgressMonitor monitor) {
|
||||
try {
|
||||
BaseProjectHelper.addMarker(iProject, AdtConstants.MARKER_TARGET,
|
||||
fmessage, -1, IMarker.SEVERITY_ERROR,
|
||||
IMarker.PRIORITY_HIGH);
|
||||
} catch (CoreException e2) {
|
||||
return e2.getStatus();
|
||||
}
|
||||
|
||||
// build jobs are run after other interactive jobs
|
||||
markerJob.setPriority(Job.BUILD);
|
||||
markerJob.schedule();
|
||||
return Status.OK_STATUS;
|
||||
}
|
||||
};
|
||||
|
||||
// build jobs are run after other interactive jobs
|
||||
markerJob.setPriority(Job.BUILD);
|
||||
markerJob.schedule();
|
||||
}
|
||||
} else {
|
||||
// no error, remove potential MARKER_TARGETs.
|
||||
try {
|
||||
if (iProject.exists()) {
|
||||
iProject.deleteMarkers(AdtConstants.MARKER_TARGET, true,
|
||||
IResource.DEPTH_INFINITE);
|
||||
}
|
||||
} catch (CoreException ce) {
|
||||
// In some cases, the workspace may be locked for modification when we pass
|
||||
// here, so we schedule a new job to put the marker after.
|
||||
Job markerJob = new Job("Android SDK: Resolving error markers") {
|
||||
@Override
|
||||
protected IStatus run(IProgressMonitor monitor) {
|
||||
try {
|
||||
iProject.deleteMarkers(AdtConstants.MARKER_TARGET, true,
|
||||
IResource.DEPTH_INFINITE);
|
||||
} catch (CoreException e2) {
|
||||
return e2.getStatus();
|
||||
}
|
||||
|
||||
return Status.OK_STATUS;
|
||||
}
|
||||
};
|
||||
|
||||
// build jobs are run after other interactive jobs
|
||||
markerJob.setPriority(Job.BUILD);
|
||||
markerJob.schedule();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -264,21 +322,114 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit
|
||||
* java doc directory. This is dynamically created when a project is opened,
|
||||
* and never saved in the project itself, so there's no risk of storing an
|
||||
* obsolete path.
|
||||
*
|
||||
* The method also stores the paths used to create the entries in the project persistent
|
||||
* properties. A new {@link AndroidClasspathContainer} can be created from the stored path
|
||||
* using the {@link #getContainerFromCache(IProject)} method.
|
||||
* @param project
|
||||
* @param target The target that contains the libraries.
|
||||
* @param targetName
|
||||
*/
|
||||
private static IClasspathEntry[] createFrameworkClasspath(IAndroidTarget target) {
|
||||
private static IClasspathEntry[] createClasspathEntries(IProject project,
|
||||
IAndroidTarget target, String targetName) {
|
||||
|
||||
// get the path from the target
|
||||
String[] paths = getTargetPaths(target);
|
||||
|
||||
// create the classpath entry from the paths
|
||||
IClasspathEntry[] entries = createClasspathEntriesFromPaths(paths);
|
||||
|
||||
// paths now contains all the path required to recreate the IClasspathEntry with no
|
||||
// target info. We encode them in a single string, with each path separated by
|
||||
// OS path separator.
|
||||
StringBuilder sb = new StringBuilder(CACHE_VERSION);
|
||||
for (String p : paths) {
|
||||
sb.append(PATH_SEPARATOR);
|
||||
sb.append(p);
|
||||
}
|
||||
|
||||
// store this in a project persistent property
|
||||
ProjectHelper.saveStringProperty(project, PROPERTY_CONTAINER_CACHE, sb.toString());
|
||||
ProjectHelper.saveStringProperty(project, PROPERTY_TARGET_NAME, targetName);
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an {@link AndroidClasspathContainer} from the project cache, if possible.
|
||||
*/
|
||||
private static AndroidClasspathContainer getContainerFromCache(IProject project) {
|
||||
// get the cached info from the project persistent properties.
|
||||
String cache = ProjectHelper.loadStringProperty(project, PROPERTY_CONTAINER_CACHE);
|
||||
String targetNameCache = ProjectHelper.loadStringProperty(project, PROPERTY_TARGET_NAME);
|
||||
if (cache == null || targetNameCache == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// the first 2 chars must match CACHE_VERSION. The 3rd char is the normal separator.
|
||||
if (cache.startsWith(CACHE_VERSION_SEP) == false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
cache = cache.substring(CACHE_VERSION_SEP.length());
|
||||
|
||||
// the cache contains multiple paths, separated by a character guaranteed to not be in
|
||||
// the path (\u001C).
|
||||
// The first 3 are for android.jar (jar, source, doc), the rest are for the optional
|
||||
// libraries and should contain at least one doc and a jar (if there are any libraries).
|
||||
// Therefore, the path count should be 3 or 5+
|
||||
String[] paths = cache.split(Pattern.quote(PATH_SEPARATOR));
|
||||
if (paths.length < 3 || paths.length == 4) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// now we check the paths actually exist.
|
||||
// There's an exception: If the source folder for android.jar does not exist, this is
|
||||
// not a problem, so we skip it.
|
||||
// Also paths[PATH_ANDROID_DOCS] is a URI to the javadoc, so we test it a bit differently.
|
||||
try {
|
||||
if (new File(paths[PATH_ANDROID_JAR]).exists() == false ||
|
||||
new File(new URI(paths[PATH_ANDROID_DOCS])).exists() == false) {
|
||||
return null;
|
||||
}
|
||||
} catch (URISyntaxException e) {
|
||||
return null;
|
||||
} finally {
|
||||
|
||||
}
|
||||
|
||||
for (int i = 3 ; i < paths.length; i++) {
|
||||
String path = paths[i];
|
||||
if (path.length() > 0) {
|
||||
File f = new File(path);
|
||||
if (f.exists() == false) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IClasspathEntry[] entries = createClasspathEntriesFromPaths(paths);
|
||||
|
||||
return new AndroidClasspathContainer(entries,
|
||||
new Path(CONTAINER_ID), targetNameCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of {@link IClasspathEntry} from a set of paths.
|
||||
* @see #getTargetPaths(IAndroidTarget)
|
||||
*/
|
||||
private static IClasspathEntry[] createClasspathEntriesFromPaths(String[] paths) {
|
||||
ArrayList<IClasspathEntry> list = new ArrayList<IClasspathEntry>();
|
||||
|
||||
// First, we create the IClasspathEntry for the framework.
|
||||
// now add the android framework to the class path.
|
||||
// create the path object.
|
||||
IPath android_lib = new Path(target.getPath(IAndroidTarget.ANDROID_JAR));
|
||||
IPath android_src = new Path(target.getPath(IAndroidTarget.SOURCES));
|
||||
|
||||
IPath android_lib = new Path(paths[PATH_ANDROID_JAR]);
|
||||
IPath android_src = new Path(paths[PATH_ANDROID_SRC]);
|
||||
|
||||
// create the java doc link.
|
||||
IClasspathAttribute cpAttribute = JavaCore.newClasspathAttribute(
|
||||
IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME, AdtPlugin.getUrlDoc());
|
||||
IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME,
|
||||
paths[PATH_ANDROID_DOCS]);
|
||||
|
||||
// create the access rule to restrict access to classes in com.android.internal
|
||||
IAccessRule accessRule = JavaCore.newAccessRule(
|
||||
@@ -292,42 +443,184 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit
|
||||
new IClasspathAttribute[] { cpAttribute },
|
||||
false // not exported.
|
||||
);
|
||||
|
||||
|
||||
list.add(frameworkClasspathEntry);
|
||||
|
||||
// now deal with optional libraries
|
||||
if (paths.length >= 5) {
|
||||
String docPath = paths[PATH_ANDROID_OPT_DOCS];
|
||||
int i = 4;
|
||||
while (i < paths.length) {
|
||||
Path jarPath = new Path(paths[i++]);
|
||||
|
||||
IClasspathAttribute[] attributes = null;
|
||||
if (docPath.length() > 0) {
|
||||
attributes = new IClasspathAttribute[] {
|
||||
JavaCore.newClasspathAttribute(
|
||||
IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME,
|
||||
docPath)
|
||||
};
|
||||
}
|
||||
|
||||
IClasspathEntry entry = JavaCore.newLibraryEntry(
|
||||
jarPath,
|
||||
null, // source attachment path
|
||||
null, // default source attachment root path.
|
||||
null,
|
||||
attributes,
|
||||
false // not exported.
|
||||
);
|
||||
list.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return list.toArray(new IClasspathEntry[list.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the projects' caches. If the cache was valid, the project is removed from the list.
|
||||
* @param projects the list of projects to check.
|
||||
*/
|
||||
public static void checkProjectsCache(ArrayList<IJavaProject> projects) {
|
||||
int i = 0;
|
||||
projectLoop: while (i < projects.size()) {
|
||||
IJavaProject javaProject = projects.get(i);
|
||||
IProject iProject = javaProject.getProject();
|
||||
|
||||
// get the target from the project and its paths
|
||||
IAndroidTarget target = Sdk.getCurrent().getTarget(javaProject.getProject());
|
||||
if (target == null) {
|
||||
// this is really not supposed to happen. This would mean there are cached paths,
|
||||
// but default.properties was deleted. Keep the project in the list to force
|
||||
// a resolve which will display the error.
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
String[] targetPaths = getTargetPaths(target);
|
||||
|
||||
// now get the cached paths
|
||||
String cache = ProjectHelper.loadStringProperty(iProject, PROPERTY_CONTAINER_CACHE);
|
||||
if (cache == null) {
|
||||
// this should not happen. We'll force resolve again anyway.
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
String[] cachedPaths = cache.split(Pattern.quote(PATH_SEPARATOR));
|
||||
if (cachedPaths.length < 3 || cachedPaths.length == 4) {
|
||||
// paths length is wrong. simply resolve the project again
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Now we compare the paths. The first 4 can be compared directly.
|
||||
// because of case sensitiveness we need to use File objects
|
||||
|
||||
if (targetPaths.length != cachedPaths.length) {
|
||||
// different paths, force resolve again.
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// compare the main paths (android.jar, main sources, main javadoc)
|
||||
if (new File(targetPaths[PATH_ANDROID_JAR]).equals(
|
||||
new File(cachedPaths[PATH_ANDROID_JAR])) == false ||
|
||||
new File(targetPaths[PATH_ANDROID_SRC]).equals(
|
||||
new File(cachedPaths[PATH_ANDROID_SRC])) == false ||
|
||||
new File(targetPaths[PATH_ANDROID_DOCS]).equals(
|
||||
new File(cachedPaths[PATH_ANDROID_DOCS])) == false) {
|
||||
// different paths, force resolve again.
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cachedPaths.length > PATH_ANDROID_OPT_DOCS) {
|
||||
// compare optional libraries javadoc
|
||||
if (new File(targetPaths[PATH_ANDROID_OPT_DOCS]).equals(
|
||||
new File(cachedPaths[PATH_ANDROID_OPT_DOCS])) == false) {
|
||||
// different paths, force resolve again.
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// testing the optional jar files is a little bit trickier.
|
||||
// The order is not guaranteed to be identical.
|
||||
// From a previous test, we do know however that there is the same number.
|
||||
// The number of libraries should be low enough that we can simply go through the
|
||||
// lists manually.
|
||||
targetLoop: for (int tpi = 4 ; tpi < targetPaths.length; tpi++) {
|
||||
String targetPath = targetPaths[tpi];
|
||||
|
||||
// look for a match in the other array
|
||||
for (int cpi = 4 ; cpi < cachedPaths.length; cpi++) {
|
||||
if (new File(targetPath).equals(new File(cachedPaths[cpi]))) {
|
||||
// found a match. Try the next targetPath
|
||||
continue targetLoop;
|
||||
}
|
||||
}
|
||||
|
||||
// if we stop here, we haven't found a match, which means there's a
|
||||
// discrepancy in the libraries. We force a resolve.
|
||||
i++;
|
||||
continue projectLoop;
|
||||
}
|
||||
}
|
||||
|
||||
// at the point the check passes, and we can remove the project from the list.
|
||||
// we do not increment i in this case.
|
||||
projects.remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the paths necessary to create the {@link IClasspathEntry} for this targets.
|
||||
* <p/>The paths are always in the same order.
|
||||
* <ul>
|
||||
* <li>Path to android.jar</li>
|
||||
* <li>Path to the source code for android.jar</li>
|
||||
* <li>Path to the javadoc for the android platform</li>
|
||||
* </ul>
|
||||
* Additionally, if there are optional libraries, the array will contain:
|
||||
* <ul>
|
||||
* <li>Path to the librairies javadoc</li>
|
||||
* <li>Path to the first .jar file</li>
|
||||
* <li>(more .jar as needed)</li>
|
||||
* </ul>
|
||||
*/
|
||||
private static String[] getTargetPaths(IAndroidTarget target) {
|
||||
ArrayList<String> paths = new ArrayList<String>();
|
||||
|
||||
// first, we get the path for android.jar
|
||||
// The order is: android.jar, source folder, docs folder
|
||||
paths.add(target.getPath(IAndroidTarget.ANDROID_JAR));
|
||||
paths.add(target.getPath(IAndroidTarget.SOURCES));
|
||||
paths.add(AdtPlugin.getUrlDoc());
|
||||
|
||||
// now deal with optional libraries.
|
||||
IOptionalLibrary[] libraries = target.getOptionalLibraries();
|
||||
if (libraries != null) {
|
||||
// all the optional libraries use the same javadoc, so we start with this
|
||||
String targetDocPath = target.getPath(IAndroidTarget.DOCS);
|
||||
if (targetDocPath != null) {
|
||||
paths.add(targetDocPath);
|
||||
} else {
|
||||
// we add an empty string, to always have the same count.
|
||||
paths.add("");
|
||||
}
|
||||
|
||||
// because different libraries could use the same jar file, we make sure we add
|
||||
// each jar file only once.
|
||||
HashSet<String> visitedJars = new HashSet<String>();
|
||||
for (IOptionalLibrary library : libraries) {
|
||||
String jarPath = library.getJarPath();
|
||||
if (visitedJars.contains(jarPath) == false) {
|
||||
visitedJars.add(jarPath);
|
||||
|
||||
// create the java doc link, if needed
|
||||
String targetDocPath = target.getPath(IAndroidTarget.DOCS);
|
||||
IClasspathAttribute[] attributes = null;
|
||||
if (targetDocPath != null) {
|
||||
attributes = new IClasspathAttribute[] {
|
||||
JavaCore.newClasspathAttribute(
|
||||
IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME,
|
||||
targetDocPath)
|
||||
};
|
||||
}
|
||||
|
||||
IClasspathEntry entry = JavaCore.newLibraryEntry(
|
||||
new Path(library.getJarPath()),
|
||||
null, // source attachment path
|
||||
null, // default source attachment root path.
|
||||
null,
|
||||
attributes,
|
||||
false // not exported.
|
||||
);
|
||||
list.add(entry);
|
||||
paths.add(jarPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list.toArray(new IClasspathEntry[list.size()]);
|
||||
return paths.toArray(new String[paths.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt.project.properties;
|
||||
|
||||
import com.android.ide.eclipse.adt.sdk.Sdk;
|
||||
import com.android.sdklib.IAndroidTarget;
|
||||
import com.android.sdkuilib.ApkConfigWidget;
|
||||
import com.android.sdkuilib.SdkTargetSelector;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
@@ -32,6 +33,8 @@ import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.ui.IWorkbenchPropertyPage;
|
||||
import org.eclipse.ui.dialogs.PropertyPage;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Property page for "Android" project.
|
||||
* This is accessible from the Package Explorer when right clicking a project and choosing
|
||||
@@ -42,6 +45,7 @@ public class AndroidPropertyPage extends PropertyPage implements IWorkbenchPrope
|
||||
|
||||
private IProject mProject;
|
||||
private SdkTargetSelector mSelector;
|
||||
private ApkConfigWidget mApkConfigWidget;
|
||||
|
||||
public AndroidPropertyPage() {
|
||||
// pass
|
||||
@@ -51,7 +55,14 @@ public class AndroidPropertyPage extends PropertyPage implements IWorkbenchPrope
|
||||
protected Control createContents(Composite parent) {
|
||||
// get the element (this is not yet valid in the constructor).
|
||||
mProject = (IProject)getElement();
|
||||
|
||||
|
||||
// get the targets from the sdk
|
||||
IAndroidTarget[] targets = null;
|
||||
if (Sdk.getCurrent() != null) {
|
||||
targets = Sdk.getCurrent().getTargets();
|
||||
}
|
||||
|
||||
// build the UI.
|
||||
Composite top = new Composite(parent, SWT.NONE);
|
||||
top.setLayoutData(new GridData(GridData.FILL_BOTH));
|
||||
top.setLayout(new GridLayout(1, false));
|
||||
@@ -59,20 +70,28 @@ public class AndroidPropertyPage extends PropertyPage implements IWorkbenchPrope
|
||||
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);
|
||||
l = new Label(top, SWT.SEPARATOR | SWT.HORIZONTAL);
|
||||
l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
|
||||
l = new Label(top, SWT.NONE);
|
||||
l.setText("Project APK Configurations");
|
||||
|
||||
mApkConfigWidget = new ApkConfigWidget(top);
|
||||
|
||||
// fill the ui
|
||||
Sdk currentSdk = Sdk.getCurrent();
|
||||
if (currentSdk != null && mProject.isOpen()) {
|
||||
// get the target
|
||||
IAndroidTarget target = currentSdk.getTarget(mProject);
|
||||
if (target != null) {
|
||||
mSelector.setSelection(target);
|
||||
}
|
||||
|
||||
// get the apk configurations
|
||||
Map<String, String> configs = currentSdk.getProjectApkConfigs(mProject);
|
||||
mApkConfigWidget.fillTable(configs);
|
||||
}
|
||||
|
||||
mSelector.setSelectionListener(new SelectionAdapter() {
|
||||
@@ -83,14 +102,20 @@ public class AndroidPropertyPage extends PropertyPage implements IWorkbenchPrope
|
||||
setValid(target != null);
|
||||
}
|
||||
});
|
||||
|
||||
if (mProject.isOpen() == false) {
|
||||
// disable the ui.
|
||||
}
|
||||
|
||||
return top;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performOk() {
|
||||
if (Sdk.getCurrent() != null) {
|
||||
Sdk.getCurrent().setProject(mProject, mSelector.getFirstSelected());
|
||||
Sdk currentSdk = Sdk.getCurrent();
|
||||
if (currentSdk != null) {
|
||||
currentSdk.setProject(mProject, mSelector.getFirstSelected(),
|
||||
mApkConfigWidget.getApkConfigs());
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.ide.eclipse.adt.sdk;
|
||||
|
||||
import com.android.ide.eclipse.adt.build.DexWrapper;
|
||||
import com.android.ide.eclipse.common.resources.IResourceRepository;
|
||||
import com.android.ide.eclipse.editors.descriptors.IDescriptorProvider;
|
||||
import com.android.ide.eclipse.editors.layout.descriptors.LayoutDescriptors;
|
||||
@@ -26,6 +27,7 @@ import com.android.ide.eclipse.editors.resources.manager.ProjectResources;
|
||||
import com.android.ide.eclipse.editors.xml.descriptors.XmlDescriptors;
|
||||
import com.android.layoutlib.api.ILayoutBridge;
|
||||
import com.android.sdklib.IAndroidTarget;
|
||||
import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
|
||||
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
@@ -42,6 +44,7 @@ public class AndroidTargetData {
|
||||
public final static int DESCRIPTOR_RESOURCES = 5;
|
||||
public final static int DESCRIPTOR_SEARCHABLE = 6;
|
||||
public final static int DESCRIPTOR_PREFERENCES = 7;
|
||||
public final static int DESCRIPTOR_APPWIDGET_PROVIDER = 8;
|
||||
|
||||
public final static class LayoutBridge {
|
||||
/** Link to the layout bridge */
|
||||
@@ -50,10 +53,14 @@ public class AndroidTargetData {
|
||||
public LoadStatus status = LoadStatus.LOADING;
|
||||
|
||||
public ClassLoader classLoader;
|
||||
|
||||
public int apiLevel;
|
||||
}
|
||||
|
||||
private final IAndroidTarget mTarget;
|
||||
|
||||
private DexWrapper mDexWrapper;
|
||||
|
||||
/**
|
||||
* mAttributeValues is a map { key => list [ values ] }.
|
||||
* The key for the map is "(element-xml-name,attribute-namespace:attribute-xml-local-name)".
|
||||
@@ -64,27 +71,35 @@ public class AndroidTargetData {
|
||||
* 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 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 AndroidManifestDescriptors mManifestDescriptors;
|
||||
private LayoutDescriptors mLayoutDescriptors;
|
||||
private MenuDescriptors mMenuDescriptors;
|
||||
private XmlDescriptors mXmlDescriptors;
|
||||
|
||||
private final Map<String, Map<String, Integer>> mEnumValueMap;
|
||||
private Map<String, Map<String, Integer>> mEnumValueMap;
|
||||
|
||||
private final ProjectResources mFrameworkResources;
|
||||
private final LayoutBridge mLayoutBridge;
|
||||
private ProjectResources mFrameworkResources;
|
||||
private LayoutBridge mLayoutBridge;
|
||||
|
||||
private boolean mLayoutBridgeInit = false;
|
||||
|
||||
AndroidTargetData(IAndroidTarget androidTarget) {
|
||||
mTarget = androidTarget;
|
||||
}
|
||||
|
||||
void setDexWrapper(DexWrapper wrapper) {
|
||||
mDexWrapper = wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an AndroidTargetData object.
|
||||
* @param optionalLibraries
|
||||
*/
|
||||
AndroidTargetData(IAndroidTarget androidTarget,
|
||||
IResourceRepository systemResourceRepository,
|
||||
void setExtraData(IResourceRepository systemResourceRepository,
|
||||
AndroidManifestDescriptors manifestDescriptors,
|
||||
LayoutDescriptors layoutDescriptors,
|
||||
MenuDescriptors menuDescriptors,
|
||||
@@ -95,10 +110,10 @@ public class AndroidTargetData {
|
||||
String[] broadcastIntentActionValues,
|
||||
String[] serviceIntentActionValues,
|
||||
String[] intentCategoryValues,
|
||||
IOptionalLibrary[] optionalLibraries,
|
||||
ProjectResources resources,
|
||||
LayoutBridge layoutBridge) {
|
||||
|
||||
mTarget = androidTarget;
|
||||
mSystemResourceRepository = systemResourceRepository;
|
||||
mManifestDescriptors = manifestDescriptors;
|
||||
mLayoutDescriptors = layoutDescriptors;
|
||||
@@ -111,6 +126,11 @@ public class AndroidTargetData {
|
||||
setPermissions(permissionValues);
|
||||
setIntentFilterActionsAndCategories(activityIntentActionValues, broadcastIntentActionValues,
|
||||
serviceIntentActionValues, intentCategoryValues);
|
||||
setOptionalLibraries(optionalLibraries);
|
||||
}
|
||||
|
||||
public DexWrapper getDexWrapper() {
|
||||
return mDexWrapper;
|
||||
}
|
||||
|
||||
public IResourceRepository getSystemResources() {
|
||||
@@ -138,6 +158,8 @@ public class AndroidTargetData {
|
||||
return ResourcesDescriptors.getInstance();
|
||||
case DESCRIPTOR_PREFERENCES:
|
||||
return mXmlDescriptors.getPreferencesProvider();
|
||||
case DESCRIPTOR_APPWIDGET_PROVIDER:
|
||||
return mXmlDescriptors.getAppWidgetProvider();
|
||||
case DESCRIPTOR_SEARCHABLE:
|
||||
return mXmlDescriptors.getSearchableProvider();
|
||||
default :
|
||||
@@ -270,6 +292,20 @@ public class AndroidTargetData {
|
||||
setValues("(service,action,android:name)", serviceIntentActions); //$NON-NLS-1$
|
||||
setValues("(category,android:name)", intentCategoryValues); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
private void setOptionalLibraries(IOptionalLibrary[] optionalLibraries) {
|
||||
String[] values;
|
||||
|
||||
if (optionalLibraries == null) {
|
||||
values = new String[0];
|
||||
} else {
|
||||
values = new String[optionalLibraries.length];
|
||||
for (int i = 0; i < optionalLibraries.length; i++) {
|
||||
values[i] = optionalLibraries[i].getName();
|
||||
}
|
||||
}
|
||||
setValues("(uses-library,android:name)", values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a (name, values) pair in the hash map.
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.ide.eclipse.adt.sdk;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.build.DexWrapper;
|
||||
import com.android.ide.eclipse.adt.sdk.AndroidTargetData.LayoutBridge;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.common.resources.AttrsXmlParser;
|
||||
@@ -91,13 +92,29 @@ public final class AndroidTargetParser {
|
||||
try {
|
||||
SubMonitor progress = SubMonitor.convert(monitor,
|
||||
String.format("Parsing SDK %1$s", mAndroidTarget.getName()),
|
||||
120);
|
||||
14);
|
||||
|
||||
AndroidTargetData targetData = new AndroidTargetData(mAndroidTarget);
|
||||
|
||||
// load DX.
|
||||
DexWrapper dexWrapper = new DexWrapper();
|
||||
IStatus res = dexWrapper.loadDex(mAndroidTarget.getPath(IAndroidTarget.DX_JAR));
|
||||
if (res != Status.OK_STATUS) {
|
||||
return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
|
||||
String.format("dx.jar loading failed for target '%1$s'",
|
||||
mAndroidTarget.getFullName()));
|
||||
}
|
||||
|
||||
// we have loaded dx.
|
||||
targetData.setDexWrapper(dexWrapper);
|
||||
progress.worked(1);
|
||||
|
||||
// parse the rest of the data.
|
||||
|
||||
AndroidJarLoader classLoader =
|
||||
new AndroidJarLoader(mAndroidTarget.getPath(IAndroidTarget.ANDROID_JAR));
|
||||
|
||||
preload(classLoader, progress.newChild(40, SubMonitor.SUPPRESS_NONE));
|
||||
progress.setWorkRemaining(80);
|
||||
|
||||
if (progress.isCanceled()) {
|
||||
return Status.CANCEL_STATUS;
|
||||
@@ -106,7 +123,7 @@ public final class AndroidTargetParser {
|
||||
// get the resource Ids.
|
||||
progress.subTask("Resource IDs");
|
||||
IResourceRepository frameworkRepository = collectResourceIds(classLoader);
|
||||
progress.worked(5);
|
||||
progress.worked(1);
|
||||
|
||||
if (progress.isCanceled()) {
|
||||
return Status.CANCEL_STATUS;
|
||||
@@ -115,7 +132,7 @@ public final class AndroidTargetParser {
|
||||
// get the permissions
|
||||
progress.subTask("Permissions");
|
||||
String[] permissionValues = collectPermissions(classLoader);
|
||||
progress.worked(5);
|
||||
progress.worked(1);
|
||||
|
||||
if (progress.isCanceled()) {
|
||||
return Status.CANCEL_STATUS;
|
||||
@@ -129,7 +146,7 @@ public final class AndroidTargetParser {
|
||||
ArrayList<String> categories = new ArrayList<String>();
|
||||
collectIntentFilterActionsAndCategories(activity_actions, broadcast_actions,
|
||||
service_actions, categories);
|
||||
progress.worked(5);
|
||||
progress.worked(1);
|
||||
|
||||
if (progress.isCanceled()) {
|
||||
return Status.CANCEL_STATUS;
|
||||
@@ -140,12 +157,14 @@ public final class AndroidTargetParser {
|
||||
AttrsXmlParser attrsXmlParser = new AttrsXmlParser(
|
||||
mAndroidTarget.getPath(IAndroidTarget.ATTRIBUTES));
|
||||
attrsXmlParser.preload();
|
||||
progress.worked(1);
|
||||
|
||||
progress.subTask("Manifest definitions");
|
||||
AttrsXmlParser attrsManifestXmlParser = new AttrsXmlParser(
|
||||
mAndroidTarget.getPath(IAndroidTarget.MANIFEST_ATTRIBUTES),
|
||||
attrsXmlParser);
|
||||
attrsManifestXmlParser.preload();
|
||||
progress.worked(1);
|
||||
|
||||
Collection<ViewClassInfo> mainList = new ArrayList<ViewClassInfo>();
|
||||
Collection<ViewClassInfo> groupList = new ArrayList<ViewClassInfo>();
|
||||
@@ -153,7 +172,7 @@ public final class AndroidTargetParser {
|
||||
// collect the layout/widgets classes
|
||||
progress.subTask("Widgets and layouts");
|
||||
collectLayoutClasses(classLoader, attrsXmlParser, mainList, groupList,
|
||||
progress.newChild(40));
|
||||
progress.newChild(1));
|
||||
|
||||
if (progress.isCanceled()) {
|
||||
return Status.CANCEL_STATUS;
|
||||
@@ -167,7 +186,7 @@ public final class AndroidTargetParser {
|
||||
mainList.clear();
|
||||
groupList.clear();
|
||||
collectPreferenceClasses(classLoader, attrsXmlParser, mainList, groupList,
|
||||
progress.newChild(5));
|
||||
progress.newChild(1));
|
||||
|
||||
if (progress.isCanceled()) {
|
||||
return Status.CANCEL_STATUS;
|
||||
@@ -184,6 +203,11 @@ public final class AndroidTargetParser {
|
||||
attrsManifestXmlParser);
|
||||
Map<String, Map<String, Integer>> enumValueMap = attrsXmlParser.getEnumFlagValues();
|
||||
|
||||
Map<String, DeclareStyleableInfo> xmlAppWidgetMap = null;
|
||||
if (mAndroidTarget.getApiVersionNumber() >= 3) {
|
||||
xmlAppWidgetMap = collectAppWidgetDefinitions(attrsXmlParser);
|
||||
}
|
||||
|
||||
if (progress.isCanceled()) {
|
||||
return Status.CANCEL_STATUS;
|
||||
}
|
||||
@@ -192,7 +216,7 @@ public final class AndroidTargetParser {
|
||||
// the PlatformData object.
|
||||
AndroidManifestDescriptors manifestDescriptors = new AndroidManifestDescriptors();
|
||||
manifestDescriptors.updateDescriptors(manifestMap);
|
||||
progress.worked(10);
|
||||
progress.worked(1);
|
||||
|
||||
if (progress.isCanceled()) {
|
||||
return Status.CANCEL_STATUS;
|
||||
@@ -200,7 +224,7 @@ public final class AndroidTargetParser {
|
||||
|
||||
LayoutDescriptors layoutDescriptors = new LayoutDescriptors();
|
||||
layoutDescriptors.updateDescriptors(layoutViewsInfo, layoutGroupsInfo);
|
||||
progress.worked(10);
|
||||
progress.worked(1);
|
||||
|
||||
if (progress.isCanceled()) {
|
||||
return Status.CANCEL_STATUS;
|
||||
@@ -208,29 +232,31 @@ public final class AndroidTargetParser {
|
||||
|
||||
MenuDescriptors menuDescriptors = new MenuDescriptors();
|
||||
menuDescriptors.updateDescriptors(xmlMenuMap);
|
||||
progress.worked(10);
|
||||
progress.worked(1);
|
||||
|
||||
if (progress.isCanceled()) {
|
||||
return Status.CANCEL_STATUS;
|
||||
}
|
||||
|
||||
XmlDescriptors xmlDescriptors = new XmlDescriptors();
|
||||
xmlDescriptors.updateDescriptors(xmlSearchableMap, preferencesInfo,
|
||||
xmlDescriptors.updateDescriptors(
|
||||
xmlSearchableMap,
|
||||
xmlAppWidgetMap,
|
||||
preferencesInfo,
|
||||
preferenceGroupsInfo);
|
||||
progress.worked(10);
|
||||
progress.worked(1);
|
||||
|
||||
// load the framework resources.
|
||||
ProjectResources resources = ResourceManager.getInstance().loadFrameworkResources(
|
||||
mAndroidTarget);
|
||||
progress.worked(10);
|
||||
progress.worked(1);
|
||||
|
||||
// now load the layout lib bridge
|
||||
LayoutBridge layoutBridge = loadLayoutBridge();
|
||||
progress.worked(10);
|
||||
progress.worked(1);
|
||||
|
||||
// and finally create the PlatformData with all that we loaded.
|
||||
AndroidTargetData targetData = new AndroidTargetData(mAndroidTarget,
|
||||
frameworkRepository,
|
||||
targetData.setExtraData(frameworkRepository,
|
||||
manifestDescriptors,
|
||||
layoutDescriptors,
|
||||
menuDescriptors,
|
||||
@@ -241,6 +267,7 @@ public final class AndroidTargetParser {
|
||||
broadcast_actions.toArray(new String[broadcast_actions.size()]),
|
||||
service_actions.toArray(new String[service_actions.size()]),
|
||||
categories.toArray(new String[categories.size()]),
|
||||
mAndroidTarget.getOptionalLibraries(),
|
||||
resources,
|
||||
layoutBridge);
|
||||
|
||||
@@ -251,10 +278,6 @@ public final class AndroidTargetParser {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -587,6 +610,31 @@ public final class AndroidTargetParser {
|
||||
return Collections.unmodifiableMap(map2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all appWidgetProviderInfo definition information from the attrs.xml and returns it.
|
||||
*
|
||||
* @param attrsXmlParser The parser of the attrs.xml file
|
||||
*/
|
||||
private Map<String, DeclareStyleableInfo> collectAppWidgetDefinitions(
|
||||
AttrsXmlParser attrsXmlParser) {
|
||||
Map<String, DeclareStyleableInfo> map = attrsXmlParser.getDeclareStyleableList();
|
||||
Map<String, DeclareStyleableInfo> map2 = new HashMap<String, DeclareStyleableInfo>();
|
||||
for (String key : new String[] { "AppWidgetProviderInfo" }) { //$NON-NLS-1$
|
||||
if (map.containsKey(key)) {
|
||||
map2.put(key, map.get(key));
|
||||
} else {
|
||||
AdtPlugin.log(IStatus.WARNING,
|
||||
"AppWidget declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$
|
||||
key, attrsXmlParser.getOsAttrsXmlPath());
|
||||
AdtPlugin.printErrorToConsole("Android Framework Parser",
|
||||
String.format("AppWidget declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$
|
||||
key, attrsXmlParser.getOsAttrsXmlPath()));
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.unmodifiableMap(map2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all manifest definition information from the attrs_manifest.xml and returns it.
|
||||
*/
|
||||
@@ -633,6 +681,15 @@ public final class AndroidTargetParser {
|
||||
layoutBridge.status = LoadStatus.FAILED;
|
||||
AdtPlugin.log(IStatus.ERROR, "Failed to load " + AndroidConstants.CLASS_BRIDGE); //$NON-NLS-1$
|
||||
} else {
|
||||
// get the api level
|
||||
try {
|
||||
layoutBridge.apiLevel = layoutBridge.bridge.getApiLevel();
|
||||
} catch (AbstractMethodError e) {
|
||||
// the first version of the api did not have this method
|
||||
layoutBridge.apiLevel = 1;
|
||||
}
|
||||
|
||||
// and mark the lib as loaded.
|
||||
layoutBridge.status = LoadStatus.LOADED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,16 +18,23 @@ package com.android.ide.eclipse.adt.sdk;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.project.internal.AndroidClasspathContainerInitializer;
|
||||
import com.android.ide.eclipse.adt.sdk.AndroidTargetData.LayoutBridge;
|
||||
import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor;
|
||||
import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IProjectListener;
|
||||
import com.android.prefs.AndroidLocation.AndroidLocationException;
|
||||
import com.android.sdklib.IAndroidTarget;
|
||||
import com.android.sdklib.ISdkLog;
|
||||
import com.android.sdklib.SdkConstants;
|
||||
import com.android.sdklib.SdkManager;
|
||||
import com.android.sdklib.avd.AvdManager;
|
||||
import com.android.sdklib.project.ApkConfigurationHelper;
|
||||
import com.android.sdklib.project.ProjectProperties;
|
||||
import com.android.sdklib.project.ProjectProperties.PropertyType;
|
||||
import com.android.sdklib.vm.VmManager;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IncrementalProjectBuilder;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jdt.core.JavaCore;
|
||||
@@ -38,6 +45,7 @@ import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Central point to load, manipulate and deal with the Android SDK. Only one SDK can be used
|
||||
@@ -48,18 +56,35 @@ import java.util.HashMap;
|
||||
*
|
||||
* To get the list of platforms or add-ons present in the SDK, call {@link #getTargets()}.
|
||||
*/
|
||||
public class Sdk {
|
||||
public class Sdk implements IProjectListener {
|
||||
private static Sdk sCurrentSdk = null;
|
||||
|
||||
private final SdkManager mManager;
|
||||
private final VmManager mVmManager;
|
||||
private final AvdManager mAvdManager;
|
||||
|
||||
private final HashMap<IProject, IAndroidTarget> mProjectMap =
|
||||
private final HashMap<IProject, IAndroidTarget> mProjectTargetMap =
|
||||
new HashMap<IProject, IAndroidTarget>();
|
||||
private final HashMap<IAndroidTarget, AndroidTargetData> mTargetMap =
|
||||
private final HashMap<IAndroidTarget, AndroidTargetData> mTargetDataMap =
|
||||
new HashMap<IAndroidTarget, AndroidTargetData>();
|
||||
private final HashMap<IProject, Map<String, String>> mProjectApkConfigMap =
|
||||
new HashMap<IProject, Map<String, String>>();
|
||||
private final String mDocBaseUrl;
|
||||
|
||||
|
||||
/**
|
||||
* Classes implementing this interface will receive notification when targets are changed.
|
||||
*/
|
||||
public interface ITargetChangeListener {
|
||||
/**
|
||||
* Sent when project has its target changed.
|
||||
*/
|
||||
void onProjectTargetChange(IProject changedProject);
|
||||
|
||||
/**
|
||||
* Called when the targets are loaded (either the SDK finished loading when Eclipse starts,
|
||||
* or the SDK is changed).
|
||||
*/
|
||||
void onTargetsLoaded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an SDK and returns an {@link Sdk} object if success.
|
||||
@@ -67,7 +92,7 @@ public class Sdk {
|
||||
*/
|
||||
public static Sdk loadSdk(String sdkLocation) {
|
||||
if (sCurrentSdk != null) {
|
||||
// manual unload?
|
||||
sCurrentSdk.dispose();
|
||||
sCurrentSdk = null;
|
||||
}
|
||||
|
||||
@@ -95,13 +120,13 @@ public class Sdk {
|
||||
// get an SdkManager object for the location
|
||||
SdkManager manager = SdkManager.createManager(sdkLocation, log);
|
||||
if (manager != null) {
|
||||
VmManager vmManager = null;
|
||||
AvdManager avdManager = null;
|
||||
try {
|
||||
vmManager = new VmManager(manager, log);
|
||||
avdManager = new AvdManager(manager, log);
|
||||
} catch (AndroidLocationException e) {
|
||||
log.error(e, "Error parsing the VMs");
|
||||
log.error(e, "Error parsing the AVDs");
|
||||
}
|
||||
sCurrentSdk = new Sdk(manager, vmManager);
|
||||
sCurrentSdk = new Sdk(manager, avdManager);
|
||||
return sCurrentSdk;
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder("Error Loading the SDK:\n");
|
||||
@@ -156,24 +181,87 @@ public class Sdk {
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates an {@link IProject} and an {@link IAndroidTarget}.
|
||||
* Sets a new target and a new list of Apk configuration for a given project.
|
||||
*
|
||||
* @param project the project to receive the new apk configurations
|
||||
* @param target The new target to set, or <code>null</code> to not change the current target.
|
||||
* @param apkConfigMap a map of apk configurations. The map contains (name, filter) where name
|
||||
* is the name of the configuration (a-zA-Z0-9 only), and filter is the comma separated list of
|
||||
* resource configuration to include in the apk (see aapt -c). Can be <code>null</code> if the
|
||||
* apk configurations should not be updated.
|
||||
*/
|
||||
public void setProject(IProject project, IAndroidTarget target) {
|
||||
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);
|
||||
public void setProject(IProject project, IAndroidTarget target,
|
||||
Map<String, String> apkConfigMap) {
|
||||
synchronized (mProjectTargetMap) {
|
||||
boolean resolveProject = false;
|
||||
boolean compileProject = false;
|
||||
boolean cleanProject = false;
|
||||
|
||||
// recompile the project if needed.
|
||||
ProjectProperties properties = ProjectProperties.load(
|
||||
project.getLocation().toOSString(), PropertyType.DEFAULT);
|
||||
if (properties == null) {
|
||||
// doesn't exist yet? we create it.
|
||||
properties = ProjectProperties.create(project.getLocation().toOSString(),
|
||||
PropertyType.DEFAULT);
|
||||
}
|
||||
|
||||
if (target != null) {
|
||||
// look for the current target of the project
|
||||
IAndroidTarget previousTarget = mProjectTargetMap.get(project);
|
||||
|
||||
if (target != previousTarget) {
|
||||
// save the target hash string in the project persistent property
|
||||
properties.setAndroidTarget(target);
|
||||
|
||||
// put it in a local map for easy access.
|
||||
mProjectTargetMap.put(project, target);
|
||||
|
||||
resolveProject = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (apkConfigMap != null) {
|
||||
// save the apk configs in the project persistent property
|
||||
cleanProject = ApkConfigurationHelper.setConfigs(properties, apkConfigMap);
|
||||
|
||||
// put it in a local map for easy access.
|
||||
mProjectApkConfigMap.put(project, apkConfigMap);
|
||||
|
||||
compileProject = true;
|
||||
}
|
||||
|
||||
// we are done with the modification. Save the property file.
|
||||
try {
|
||||
properties.save();
|
||||
} catch (IOException e) {
|
||||
AdtPlugin.log(e, "Failed to save default.properties for project '%s'",
|
||||
project.getName());
|
||||
}
|
||||
|
||||
if (resolveProject) {
|
||||
// force a resolve of the project by updating the classpath container.
|
||||
IJavaProject javaProject = JavaCore.create(project);
|
||||
AndroidClasspathContainerInitializer.updateProjects(
|
||||
new IJavaProject[] { javaProject });
|
||||
} else if (compileProject) {
|
||||
// If there was removed configs, we clean instead of build
|
||||
// (to remove the obsolete ap_ and apk file from removed configs).
|
||||
try {
|
||||
project.build(cleanProject ?
|
||||
IncrementalProjectBuilder.CLEAN_BUILD :
|
||||
IncrementalProjectBuilder.FULL_BUILD,
|
||||
null);
|
||||
} catch (CoreException e) {
|
||||
// failed to build? force resolve instead.
|
||||
IJavaProject javaProject = JavaCore.create(project);
|
||||
AndroidClasspathContainerInitializer.updateProjects(
|
||||
new IJavaProject[] { javaProject });
|
||||
}
|
||||
}
|
||||
|
||||
// finally, update the opened editors.
|
||||
if (resolveProject) {
|
||||
AdtPlugin.getDefault().updateTargetListener(project);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,11 +270,11 @@ public class Sdk {
|
||||
* Returns the {@link IAndroidTarget} object associated with the given {@link IProject}.
|
||||
*/
|
||||
public IAndroidTarget getTarget(IProject project) {
|
||||
synchronized (mProjectMap) {
|
||||
IAndroidTarget target = mProjectMap.get(project);
|
||||
synchronized (mProjectTargetMap) {
|
||||
IAndroidTarget target = mProjectTargetMap.get(project);
|
||||
if (target == null) {
|
||||
// get the value from the project persistent property.
|
||||
String targetHashString = getProjectTargetHashString(project);
|
||||
String targetHashString = loadProjectProperties(project, this);
|
||||
|
||||
if (targetHashString != null) {
|
||||
target = mManager.getTargetFromHashString(targetHashString);
|
||||
@@ -197,16 +285,26 @@ public class Sdk {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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()}.
|
||||
* Parses the project properties and returns the hash string uniquely identifying the
|
||||
* target of the given project.
|
||||
* <p/>
|
||||
* This methods reads the content of the <code>default.properties</code> file present in
|
||||
* the root folder of the project.
|
||||
* <p/>The returned string is equivalent to the return of {@link IAndroidTarget#hashString()}.
|
||||
* @param project The project for which to return the target hash string.
|
||||
* @param sdkStorage The sdk in which to store the Apk Configs. Can be null.
|
||||
* @return the hash string or null if the project does not have a target set.
|
||||
*/
|
||||
public static String getProjectTargetHashString(IProject project) {
|
||||
private static String loadProjectProperties(IProject project, Sdk sdkStorage) {
|
||||
// load the default.properties from the project folder.
|
||||
ProjectProperties properties = ProjectProperties.load(project.getLocation().toOSString(),
|
||||
IPath location = project.getLocation();
|
||||
if (location == null) { // can return null when the project is being deleted.
|
||||
// do nothing and return null;
|
||||
return null;
|
||||
}
|
||||
ProjectProperties properties = ProjectProperties.load(location.toOSString(),
|
||||
PropertyType.DEFAULT);
|
||||
if (properties == null) {
|
||||
AdtPlugin.log(IStatus.ERROR, "Failed to load properties file for project '%s'",
|
||||
@@ -214,11 +312,32 @@ public class Sdk {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (sdkStorage != null) {
|
||||
Map<String, String> configMap = ApkConfigurationHelper.getConfigs(properties);
|
||||
|
||||
if (configMap != null) {
|
||||
sdkStorage.mProjectApkConfigMap.put(project, configMap);
|
||||
}
|
||||
}
|
||||
|
||||
return properties.getProperty(ProjectProperties.PROPERTY_TARGET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hash string uniquely identifying the target of a project.
|
||||
* <p/>
|
||||
* This methods reads the content of the <code>default.properties</code> file present in
|
||||
* the root folder of the project.
|
||||
* <p/>The string is equivalent to the return of {@link IAndroidTarget#hashString()}.
|
||||
* @param project The project for which to return the target hash string.
|
||||
* @return the hash string or null if the project does not have a target set.
|
||||
*/
|
||||
public static String getProjectTargetHashString(IProject project) {
|
||||
return loadProjectProperties(project, null /*storeConfigs*/);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a target hash string in a project's persistent preferences/property storage.
|
||||
* Sets a target hash string in given project's <code>default.properties</code> file.
|
||||
* @param project The project in which to save the hash string.
|
||||
* @param targetHashString The target hash string to save. This must be the result from
|
||||
* {@link IAndroidTarget#hashString()}.
|
||||
@@ -249,31 +368,51 @@ public class Sdk {
|
||||
* Return the {@link AndroidTargetData} for a given {@link IAndroidTarget}.
|
||||
*/
|
||||
public AndroidTargetData getTargetData(IAndroidTarget target) {
|
||||
synchronized (mTargetMap) {
|
||||
return mTargetMap.get(target);
|
||||
synchronized (mTargetDataMap) {
|
||||
return mTargetDataMap.get(target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link VmManager}. If the VmManager failed to parse the VM folder, this could
|
||||
* be <code>null</code>.
|
||||
* Returns the configuration map for a given project.
|
||||
* <p/>The Map key are name to be used in the apk filename, while the values are comma separated
|
||||
* config values. The config value can be passed directly to aapt through the -c option.
|
||||
*/
|
||||
public VmManager getVmManager() {
|
||||
return mVmManager;
|
||||
public Map<String, String> getProjectApkConfigs(IProject project) {
|
||||
return mProjectApkConfigMap.get(project);
|
||||
}
|
||||
|
||||
private Sdk(SdkManager manager, VmManager vmManager) {
|
||||
/**
|
||||
* Returns the {@link AvdManager}. If the AvdManager failed to parse the AVD folder, this could
|
||||
* be <code>null</code>.
|
||||
*/
|
||||
public AvdManager getAvdManager() {
|
||||
return mAvdManager;
|
||||
}
|
||||
|
||||
private Sdk(SdkManager manager, AvdManager avdManager) {
|
||||
mManager = manager;
|
||||
mVmManager = vmManager;
|
||||
mAvdManager = avdManager;
|
||||
|
||||
// listen to projects closing
|
||||
ResourceMonitor monitor = ResourceMonitor.getMonitor();
|
||||
monitor.addProjectListener(this);
|
||||
|
||||
// pre-compute some paths
|
||||
mDocBaseUrl = getDocumentationBaseUrl(mManager.getLocation() +
|
||||
SdkConstants.OS_SDK_DOCS_FOLDER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans and unloads the SDK.
|
||||
*/
|
||||
private void dispose() {
|
||||
ResourceMonitor.getMonitor().removeProjectListener(this);
|
||||
}
|
||||
|
||||
void setTargetData(IAndroidTarget target, AndroidTargetData data) {
|
||||
synchronized (mTargetMap) {
|
||||
mTargetMap.put(target, data);
|
||||
synchronized (mTargetDataMap) {
|
||||
mTargetDataMap.put(target, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,6 +453,40 @@ public class Sdk {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void projectClosed(IProject project) {
|
||||
// get the target project
|
||||
synchronized (mProjectTargetMap) {
|
||||
IAndroidTarget target = mProjectTargetMap.get(project);
|
||||
if (target != null) {
|
||||
// get the bridge for the target, and clear the cache for this project.
|
||||
AndroidTargetData data = mTargetDataMap.get(target);
|
||||
if (data != null) {
|
||||
LayoutBridge bridge = data.getLayoutBridge();
|
||||
if (bridge != null && bridge.status == LoadStatus.LOADED) {
|
||||
bridge.bridge.clearCaches(project);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now remove the project for the maps.
|
||||
mProjectTargetMap.remove(project);
|
||||
mProjectApkConfigMap.remove(project);
|
||||
}
|
||||
}
|
||||
|
||||
public void projectDeleted(IProject project) {
|
||||
projectClosed(project);
|
||||
}
|
||||
|
||||
public void projectOpened(IProject project) {
|
||||
// ignore this. The project will be added to the map the first time the target needs
|
||||
// to be resolved.
|
||||
}
|
||||
|
||||
public void projectOpenedWithWorkspace(IProject project) {
|
||||
// ignore this. The project will be added to the map the first time the target needs
|
||||
// to be resolved.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.wizards.actions;
|
||||
|
||||
import com.android.ide.eclipse.adt.wizards.newproject.NewProjectWizard;
|
||||
|
||||
import org.eclipse.jface.action.IAction;
|
||||
import org.eclipse.ui.IWorkbenchWizard;
|
||||
|
||||
/**
|
||||
* Delegate for the toolbar action "Android Project".
|
||||
* It displays the Android New Project wizard.
|
||||
*/
|
||||
public class NewProjectAction extends OpenWizardAction {
|
||||
|
||||
@Override
|
||||
protected IWorkbenchWizard instanciateWizard(IAction action) {
|
||||
return new NewProjectWizard();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.wizards.actions;
|
||||
|
||||
import com.android.ide.eclipse.editors.wizards.NewXmlFileWizard;
|
||||
|
||||
import org.eclipse.jface.action.IAction;
|
||||
import org.eclipse.ui.IWorkbenchWizard;
|
||||
|
||||
/**
|
||||
* Delegate for the toolbar action "Android Project".
|
||||
* It displays the Android New XML file wizard.
|
||||
*/
|
||||
public class NewXmlFileAction extends OpenWizardAction {
|
||||
|
||||
@Override
|
||||
protected IWorkbenchWizard instanciateWizard(IAction action) {
|
||||
return new NewXmlFileWizard();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.wizards.actions;
|
||||
|
||||
import org.eclipse.jface.action.IAction;
|
||||
import org.eclipse.jface.viewers.ISelection;
|
||||
import org.eclipse.jface.viewers.IStructuredSelection;
|
||||
import org.eclipse.jface.viewers.StructuredSelection;
|
||||
import org.eclipse.jface.wizard.WizardDialog;
|
||||
import org.eclipse.swt.graphics.Point;
|
||||
import org.eclipse.swt.widgets.Shell;
|
||||
import org.eclipse.ui.IEditorInput;
|
||||
import org.eclipse.ui.IEditorPart;
|
||||
import org.eclipse.ui.IWorkbench;
|
||||
import org.eclipse.ui.IWorkbenchPart;
|
||||
import org.eclipse.ui.IWorkbenchWindow;
|
||||
import org.eclipse.ui.IWorkbenchWindowActionDelegate;
|
||||
import org.eclipse.ui.IWorkbenchWizard;
|
||||
import org.eclipse.ui.PlatformUI;
|
||||
import org.eclipse.ui.internal.IWorkbenchHelpContextIds;
|
||||
import org.eclipse.ui.internal.LegacyResourceSupport;
|
||||
import org.eclipse.ui.internal.actions.NewWizardShortcutAction;
|
||||
import org.eclipse.ui.internal.util.Util;
|
||||
|
||||
/**
|
||||
* An abstract action that displays one of our wizards.
|
||||
* Derived classes must provide the actual wizard to display.
|
||||
*/
|
||||
/*package*/ abstract class OpenWizardAction implements IWorkbenchWindowActionDelegate {
|
||||
|
||||
/**
|
||||
* The wizard dialog width, extracted from {@link NewWizardShortcutAction}
|
||||
*/
|
||||
private static final int SIZING_WIZARD_WIDTH = 500;
|
||||
|
||||
/**
|
||||
* The wizard dialog height, extracted from {@link NewWizardShortcutAction}
|
||||
*/
|
||||
private static final int SIZING_WIZARD_HEIGHT = 500;
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.eclipse.ui.IWorkbenchWindowActionDelegate#dispose()
|
||||
*/
|
||||
public void dispose() {
|
||||
// pass
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.eclipse.ui.IWorkbenchWindowActionDelegate#init(org.eclipse.ui.IWorkbenchWindow)
|
||||
*/
|
||||
public void init(IWorkbenchWindow window) {
|
||||
// pass
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens and display the Android New Project Wizard.
|
||||
* <p/>
|
||||
* Most of this implementation is extracted from {@link NewWizardShortcutAction#run()}.
|
||||
*
|
||||
* @see org.eclipse.ui.IActionDelegate#run(org.eclipse.jface.action.IAction)
|
||||
*/
|
||||
public void run(IAction action) {
|
||||
|
||||
// get the workbench and the current window
|
||||
IWorkbench workbench = PlatformUI.getWorkbench();
|
||||
IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
|
||||
|
||||
// This code from NewWizardShortcutAction#run() gets the current window selection
|
||||
// and converts it to a workbench structured selection for the wizard, if possible.
|
||||
ISelection selection = window.getSelectionService().getSelection();
|
||||
IStructuredSelection selectionToPass = StructuredSelection.EMPTY;
|
||||
if (selection instanceof IStructuredSelection) {
|
||||
selectionToPass = (IStructuredSelection) selection;
|
||||
} else {
|
||||
// Build the selection from the IFile of the editor
|
||||
IWorkbenchPart part = window.getPartService().getActivePart();
|
||||
if (part instanceof IEditorPart) {
|
||||
IEditorInput input = ((IEditorPart) part).getEditorInput();
|
||||
Class<?> fileClass = LegacyResourceSupport.getFileClass();
|
||||
if (input != null && fileClass != null) {
|
||||
Object file = Util.getAdapter(input, fileClass);
|
||||
if (file != null) {
|
||||
selectionToPass = new StructuredSelection(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the wizard and initialize it with the selection
|
||||
IWorkbenchWizard wizard = instanciateWizard(action);
|
||||
wizard.init(workbench, selectionToPass);
|
||||
|
||||
// It's not visible yet until a dialog is created and opened
|
||||
Shell parent = window.getShell();
|
||||
WizardDialog dialog = new WizardDialog(parent, wizard);
|
||||
dialog.create();
|
||||
|
||||
// This code comes straight from NewWizardShortcutAction#run()
|
||||
Point defaultSize = dialog.getShell().getSize();
|
||||
dialog.getShell().setSize(
|
||||
Math.max(SIZING_WIZARD_WIDTH, defaultSize.x),
|
||||
Math.max(SIZING_WIZARD_HEIGHT, defaultSize.y));
|
||||
window.getWorkbench().getHelpSystem().setHelp(dialog.getShell(),
|
||||
IWorkbenchHelpContextIds.NEW_WIZARD_SHORTCUT);
|
||||
|
||||
dialog.open();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by {@link #run(IAction)} to instantiate the actual wizard.
|
||||
*
|
||||
* @param action The action parameter from {@link #run(IAction)}.
|
||||
* @return A new wizard instance. Must not be null.
|
||||
*/
|
||||
protected abstract IWorkbenchWizard instanciateWizard(IAction action);
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.eclipse.ui.IActionDelegate#selectionChanged(org.eclipse.jface.action.IAction, org.eclipse.jface.viewers.ISelection)
|
||||
*/
|
||||
public void selectionChanged(IAction action, ISelection selection) {
|
||||
// pass
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,7 +24,7 @@ package com.android.ide.eclipse.adt.wizards.newproject;
|
||||
|
||||
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.sdklib.IAndroidTarget;
|
||||
import com.android.sdklib.SdkConstants;
|
||||
import com.android.sdklib.project.ProjectProperties;
|
||||
@@ -821,26 +821,36 @@ public class NewProjectCreationPage extends WizardPage {
|
||||
|
||||
Path path = new Path(f.getPath());
|
||||
String osPath = path.append(AndroidConstants.FN_ANDROID_MANIFEST).toOSString();
|
||||
AndroidManifestHelper manifest = new AndroidManifestHelper(osPath);
|
||||
if (!manifest.exists()) {
|
||||
|
||||
AndroidManifestParser manifestData = AndroidManifestParser.parseForData(osPath);
|
||||
if (manifestData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String packageName = null;
|
||||
String activityName = null;
|
||||
String minSdkVersion = null;
|
||||
int minSdkVersion = 0; // 0 means no minSdkVersion provided in the manifest
|
||||
try {
|
||||
packageName = manifest.getPackageName();
|
||||
activityName = manifest.getActivityName(1);
|
||||
minSdkVersion = manifest.getMinSdkVersion();
|
||||
packageName = manifestData.getPackage();
|
||||
minSdkVersion = manifestData.getApiLevelRequirement();
|
||||
|
||||
// try to get the first launcher activity. If none, just take the first activity.
|
||||
activityName = manifestData.getLauncherActivity();
|
||||
if (activityName == null) {
|
||||
String[] activities = manifestData.getActivities();
|
||||
if (activities != null && activities.length > 0) {
|
||||
activityName = activities[0];
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore exceptions
|
||||
}
|
||||
|
||||
|
||||
if (packageName != null && packageName.length() > 0) {
|
||||
mPackageNameField.setText(packageName);
|
||||
}
|
||||
|
||||
activityName = AndroidManifestParser.extractActivityName(activityName, packageName);
|
||||
|
||||
if (activityName != null && activityName.length() > 0) {
|
||||
mInternalActivityNameUpdate = true;
|
||||
@@ -917,12 +927,10 @@ public class NewProjectCreationPage extends WizardPage {
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundTarget && minSdkVersion != null) {
|
||||
if (!foundTarget && minSdkVersion > 0) {
|
||||
try {
|
||||
int sdkVersion = Integer.parseInt(minSdkVersion);
|
||||
|
||||
for (IAndroidTarget target : mSdkTargetSelector.getTargets()) {
|
||||
if (target.getApiVersionNumber() == sdkVersion) {
|
||||
if (target.getApiVersionNumber() == minSdkVersion) {
|
||||
mSdkTargetSelector.setSelection(target);
|
||||
foundTarget = true;
|
||||
break;
|
||||
@@ -945,7 +953,8 @@ public class NewProjectCreationPage extends WizardPage {
|
||||
|
||||
if (!foundTarget) {
|
||||
mInternalMinSdkVersionUpdate = true;
|
||||
mMinSdkVersionField.setText(minSdkVersion == null ? "" : minSdkVersion); //$NON-NLS-1$
|
||||
mMinSdkVersionField.setText(
|
||||
minSdkVersion <= 0 ? "" : Integer.toString(minSdkVersion)); //$NON-NLS-1$
|
||||
mInternalMinSdkVersionUpdate = false;
|
||||
}
|
||||
}
|
||||
@@ -1072,8 +1081,8 @@ public class NewProjectCreationPage extends WizardPage {
|
||||
|
||||
// Check there's an android manifest in the directory
|
||||
String osPath = path.append(AndroidConstants.FN_ANDROID_MANIFEST).toOSString();
|
||||
AndroidManifestHelper manifest = new AndroidManifestHelper(osPath);
|
||||
if (!manifest.exists()) {
|
||||
File manifestFile = new File(osPath);
|
||||
if (!manifestFile.isFile()) {
|
||||
return setStatus(
|
||||
String.format("File %1$s not found in %2$s.",
|
||||
AndroidConstants.FN_ANDROID_MANIFEST, f.getName()),
|
||||
@@ -1081,15 +1090,22 @@ public class NewProjectCreationPage extends WizardPage {
|
||||
}
|
||||
|
||||
// Parse it and check the important fields.
|
||||
String packageName = manifest.getPackageName();
|
||||
AndroidManifestParser manifestData = AndroidManifestParser.parseForData(osPath);
|
||||
if (manifestData == null) {
|
||||
return setStatus(
|
||||
String.format("File %1$s could not be parsed.", osPath),
|
||||
MSG_ERROR);
|
||||
}
|
||||
|
||||
String packageName = manifestData.getPackage();
|
||||
if (packageName == null || packageName.length() == 0) {
|
||||
return setStatus(
|
||||
String.format("No package name defined in %1$s.", osPath),
|
||||
MSG_ERROR);
|
||||
}
|
||||
|
||||
String activityName = manifest.getActivityName(1);
|
||||
if (activityName == null || activityName.length() == 0) {
|
||||
String[] activities = manifestData.getActivities();
|
||||
if (activities == null || activities.length == 0) {
|
||||
// This is acceptable now as long as no activity needs to be created
|
||||
if (isCreateActivity()) {
|
||||
return setStatus(
|
||||
@@ -1097,7 +1113,7 @@ public class NewProjectCreationPage extends WizardPage {
|
||||
MSG_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If there's already a .project, tell the user to use import instead.
|
||||
if (path.append(".project").toFile().exists()) { //$NON-NLS-1$
|
||||
return setStatus("An Eclipse project already exists in this directory. Consider using File > Import > Existing Project instead.",
|
||||
|
||||
@@ -105,6 +105,8 @@ public class NewProjectWizard extends Wizard implements INewWizard {
|
||||
SdkConstants.FD_LAYOUT + AndroidConstants.WS_SEP;
|
||||
private static final String VALUES_DIRECTORY =
|
||||
SdkConstants.FD_VALUES + AndroidConstants.WS_SEP;
|
||||
private static final String GEN_SRC_DIRECTORY =
|
||||
SdkConstants.FD_GEN_SOURCES + AndroidConstants.WS_SEP;
|
||||
|
||||
private static final String TEMPLATES_DIRECTORY = "templates/"; //$NON-NLS-1$
|
||||
private static final String TEMPLATE_MANIFEST = TEMPLATES_DIRECTORY
|
||||
@@ -114,7 +116,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
|
||||
private static final String TEMPLATE_USES_SDK = TEMPLATES_DIRECTORY
|
||||
+ "uses-sdk.template"; //$NON-NLS-1$
|
||||
private static final String TEMPLATE_INTENT_LAUNCHER = TEMPLATES_DIRECTORY
|
||||
+ "launcher_intent_filter.template"; //$NON-NLS-1$
|
||||
+ "launcher_intent_filter.template"; //$NON-NLS-1$
|
||||
|
||||
private static final String TEMPLATE_STRINGS = TEMPLATES_DIRECTORY
|
||||
+ "strings.template"; //$NON-NLS-1$
|
||||
@@ -341,15 +343,26 @@ 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[] { (String) parameters.get(PARAM_SRC_FOLDER) };
|
||||
addDefaultDirectories(project, AndroidConstants.WS_ROOT, sourceFolder, monitor);
|
||||
String[] sourceFolders = new String[] {
|
||||
(String) parameters.get(PARAM_SRC_FOLDER),
|
||||
GEN_SRC_DIRECTORY
|
||||
};
|
||||
addDefaultDirectories(project, AndroidConstants.WS_ROOT, sourceFolders, monitor);
|
||||
|
||||
// Create the resource folders in the project if they don't already exist.
|
||||
addDefaultDirectories(project, RES_DIRECTORY, RES_DIRECTORIES, monitor);
|
||||
|
||||
// Setup class path
|
||||
// Setup class path: mark folders as source folders
|
||||
IJavaProject javaProject = JavaCore.create(project);
|
||||
setupSourceFolder(javaProject, sourceFolder[0], monitor);
|
||||
for (String sourceFolder : sourceFolders) {
|
||||
setupSourceFolder(javaProject, sourceFolder, monitor);
|
||||
}
|
||||
|
||||
// Mark the gen source folder as derived
|
||||
IFolder genSrcFolder = project.getFolder(AndroidConstants.WS_ROOT + GEN_SRC_DIRECTORY);
|
||||
if (genSrcFolder.exists()) {
|
||||
genSrcFolder.setDerived(true);
|
||||
}
|
||||
|
||||
if (((Boolean) parameters.get(PARAM_IS_NEW_PROJECT)).booleanValue()) {
|
||||
// Create files in the project if they don't already exist
|
||||
@@ -359,7 +372,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
|
||||
addIcon(project, monitor);
|
||||
|
||||
// Create the default package components
|
||||
addSampleCode(project, sourceFolder[0], parameters, stringDictionary, monitor);
|
||||
addSampleCode(project, sourceFolders[0], parameters, stringDictionary, monitor);
|
||||
|
||||
// add the string definition file if needed
|
||||
if (stringDictionary.size() > 0) {
|
||||
@@ -371,7 +384,8 @@ public class NewProjectWizard extends Wizard implements INewWizard {
|
||||
monitor);
|
||||
}
|
||||
|
||||
Sdk.getCurrent().setProject(project, (IAndroidTarget) parameters.get(PARAM_SDK_TARGET));
|
||||
Sdk.getCurrent().setProject(project, (IAndroidTarget) parameters.get(PARAM_SDK_TARGET),
|
||||
null /* apkConfigMap*/);
|
||||
|
||||
// Fix the project to make sure all properties are as expected.
|
||||
// Necessary for existing projects and good for new ones to.
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.ide.eclipse.common;
|
||||
import com.android.sdklib.SdkConstants;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Constant definition class.<br>
|
||||
@@ -49,17 +50,6 @@ public class AndroidConstants {
|
||||
/** 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. '/'. */
|
||||
@@ -97,10 +87,7 @@ public class AndroidConstants {
|
||||
|
||||
/** Name of the manifest file, i.e. "AndroidManifest.xml". */
|
||||
public static final String FN_ANDROID_MANIFEST = "AndroidManifest.xml"; //$NON-NLS-1$
|
||||
public static final String FN_PROJECT_AIDL = "project.aidl"; //$NON-NLS-1$
|
||||
|
||||
/** 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$
|
||||
|
||||
@@ -114,20 +101,21 @@ public class AndroidConstants {
|
||||
public final static String FN_CLASSES_DEX = "classes.dex"; //$NON-NLS-1$
|
||||
/** Temporary packaged resources file name, i.e. "resources.ap_" */
|
||||
public final static String FN_RESOURCES_AP_ = "resources.ap_"; //$NON-NLS-1$
|
||||
/** Temporary packaged resources file name for a specific set of configuration */
|
||||
public final static String FN_RESOURCES_S_AP_ = "resources-%s.ap_"; //$NON-NLS-1$
|
||||
public final static Pattern PATTERN_RESOURCES_S_AP_ =
|
||||
Pattern.compile("resources-.*\\.ap_", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
public final static String FN_ADB = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
|
||||
public final static String FN_ADB =
|
||||
(SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) ?
|
||||
"adb.exe" : "adb"; //$NON-NLS-1$ //$NON-NLS-2$
|
||||
|
||||
public final static String FN_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) ?
|
||||
public final static String FN_EMULATOR =
|
||||
(SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) ?
|
||||
"emulator.exe" : "emulator"; //$NON-NLS-1$ //$NON-NLS-2$
|
||||
|
||||
public final static String FN_TRACEVIEW = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
|
||||
public final static String FN_TRACEVIEW =
|
||||
(SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) ?
|
||||
"traceview.exe" : "traceview"; //$NON-NLS-1$ //$NON-NLS-2$
|
||||
|
||||
/** Absolute path of the workspace root, i.e. "/" */
|
||||
@@ -147,11 +135,6 @@ public class AndroidConstants {
|
||||
* 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$
|
||||
@@ -164,8 +147,11 @@ public class AndroidConstants {
|
||||
/** 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$
|
||||
/** aapt marker error when running the compile command */
|
||||
public final static String MARKER_AAPT_COMPILE = COMMON_PLUGIN_ID + ".aaptProblem"; //$NON-NLS-1$
|
||||
|
||||
/** aapt marker error when running the package command */
|
||||
public final static String MARKER_AAPT_PACKAGE = COMMON_PLUGIN_ID + ".aapt2Problem"; //$NON-NLS-1$
|
||||
|
||||
/** XML marker error. */
|
||||
public final static String MARKER_XML = COMMON_PLUGIN_ID + ".xmlProblem"; //$NON-NLS-1$
|
||||
@@ -229,22 +215,4 @@ public class AndroidConstants {
|
||||
/** 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,241 +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.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 mXPath.evaluate("/manifest/@package", getSource()); //$NON-NLS-1$
|
||||
} 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 minSdkVersion defined in the manifest file.
|
||||
*
|
||||
* @return A String object with the package or null if any error happened.
|
||||
*/
|
||||
public String getMinSdkVersion() {
|
||||
try {
|
||||
return mXPath.evaluate("/manifest/uses-sdk/@" //$NON-NLS-1$
|
||||
+ AndroidXPathFactory.DEFAULT_NS_PREFIX
|
||||
+ ":minSdkVersion", getSource()); //$NON-NLS-1$
|
||||
} 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 index The 1-based index of the activity to return.
|
||||
* @return A String object with the activity or null if any error happened.
|
||||
*/
|
||||
public String getActivityName(int index) {
|
||||
try {
|
||||
return mXPath.evaluate("/manifest/application/activity[" //$NON-NLS-1$
|
||||
+ index
|
||||
+ "]/@" //$NON-NLS-1$
|
||||
+ AndroidXPathFactory.DEFAULT_NS_PREFIX +":name", //$NON-NLS-1$
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,6 +22,8 @@ import com.android.sdklib.SdkConstants;
|
||||
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.resources.IMarker;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.xml.sax.Attributes;
|
||||
@@ -30,6 +32,8 @@ import org.xml.sax.Locator;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.SAXParseException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
@@ -56,6 +60,8 @@ public class AndroidManifestParser {
|
||||
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 String NODE_INSTRUMENTATION = "instrumentation"; //$NON-NLS-1$
|
||||
private final static String NODE_USES_LIBRARY = "uses-library"; //$NON-NLS-1$
|
||||
|
||||
private final static int LEVEL_MANIFEST = 0;
|
||||
private final static int LEVEL_APPLICATION = 1;
|
||||
@@ -66,6 +72,12 @@ public class AndroidManifestParser {
|
||||
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$
|
||||
|
||||
/**
|
||||
* XML error & data handler used when parsing the AndroidManifest.xml file.
|
||||
* <p/>
|
||||
* This serves both as an {@link XmlErrorHandler} to report errors and as a data repository
|
||||
* to collect data from the manifest.
|
||||
*/
|
||||
private static class ManifestHandler extends XmlErrorHandler {
|
||||
|
||||
//--- data read from the parsing
|
||||
@@ -82,6 +94,10 @@ public class AndroidManifestParser {
|
||||
private Boolean mDebuggable = null;
|
||||
/** API level requirement. if 0 the attribute was not present. */
|
||||
private int mApiLevelRequirement = 0;
|
||||
/** List of all instrumentations declared by the manifest */
|
||||
private final ArrayList<String> mInstrumentations = new ArrayList<String>();
|
||||
/** List of all libraries in use declared by the manifest */
|
||||
private final ArrayList<String> mLibraries = new ArrayList<String>();
|
||||
|
||||
//--- temporary data/flags used during parsing
|
||||
private IJavaProject mJavaProject;
|
||||
@@ -95,12 +111,13 @@ public class AndroidManifestParser {
|
||||
private Locator mLocator;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param manifestFile
|
||||
* @param errorListener
|
||||
* @param gatherData
|
||||
* @param javaProject
|
||||
* @param markErrors
|
||||
* Creates a new {@link ManifestHandler}, which is also an {@link XmlErrorHandler}.
|
||||
*
|
||||
* @param manifestFile The manifest file being parsed. Can be null.
|
||||
* @param errorListener An optional error listener.
|
||||
* @param gatherData True if data should be gathered.
|
||||
* @param javaProject The java project holding the manifest file. Can be null.
|
||||
* @param markErrors True if errors should be marked as Eclipse Markers on the resource.
|
||||
*/
|
||||
ManifestHandler(IFile manifestFile, XmlErrorListener errorListener,
|
||||
boolean gatherData, IJavaProject javaProject, boolean markErrors) {
|
||||
@@ -160,6 +177,23 @@ public class AndroidManifestParser {
|
||||
return mApiLevelRequirement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of instrumentations found in the manifest.
|
||||
* @return An array of instrumentation names, or empty if no instrumentations were
|
||||
* found.
|
||||
*/
|
||||
String[] getInstrumentations() {
|
||||
return mInstrumentations.toArray(new String[mInstrumentations.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of libraries in use found in the manifest.
|
||||
* @return An array of library names, or empty if no libraries were found.
|
||||
*/
|
||||
String[] getUsesLibraries() {
|
||||
return mLibraries.toArray(new String[mLibraries.size()]);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.xml.sax.helpers.DefaultHandler#setDocumentLocator(org.xml.sax.Locator)
|
||||
*/
|
||||
@@ -217,7 +251,13 @@ public class AndroidManifestParser {
|
||||
} catch (NumberFormatException e) {
|
||||
handleError(e, -1 /* lineNumber */);
|
||||
}
|
||||
}
|
||||
} else if (NODE_INSTRUMENTATION.equals(localName)) {
|
||||
value = getAttributeValue(attributes, ATTRIBUTE_NAME,
|
||||
true /* hasNamespace */);
|
||||
if (value != null) {
|
||||
mInstrumentations.add(value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LEVEL_ACTIVITY:
|
||||
if (NODE_ACTIVITY.equals(localName)) {
|
||||
@@ -232,7 +272,13 @@ public class AndroidManifestParser {
|
||||
} else if (NODE_PROVIDER.equals(localName)) {
|
||||
processNode(attributes, AndroidConstants.CLASS_CONTENTPROVIDER);
|
||||
mValidLevel++;
|
||||
}
|
||||
} else if (NODE_USES_LIBRARY.equals(localName)) {
|
||||
value = getAttributeValue(attributes, ATTRIBUTE_NAME,
|
||||
true /* hasNamespace */);
|
||||
if (value != null) {
|
||||
mLibraries.add(value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case LEVEL_INTENT_FILTER:
|
||||
// only process this level if we are in an activity
|
||||
@@ -355,8 +401,7 @@ public class AndroidManifestParser {
|
||||
String activityName = getAttributeValue(attributes, ATTRIBUTE_NAME,
|
||||
true /* hasNamespace */);
|
||||
if (activityName != null) {
|
||||
mCurrentActivity = AndroidManifestHelper.combinePackageAndClassName(mPackage,
|
||||
activityName);
|
||||
mCurrentActivity = combinePackageAndClassName(mPackage, activityName);
|
||||
mActivities.add(mCurrentActivity);
|
||||
|
||||
if (mMarkErrors) {
|
||||
@@ -387,8 +432,7 @@ public class AndroidManifestParser {
|
||||
String serviceName = getAttributeValue(attributes, ATTRIBUTE_NAME,
|
||||
true /* hasNamespace */);
|
||||
if (serviceName != null) {
|
||||
serviceName = AndroidManifestHelper.combinePackageAndClassName(mPackage,
|
||||
serviceName);
|
||||
serviceName = combinePackageAndClassName(mPackage, serviceName);
|
||||
|
||||
if (mMarkErrors) {
|
||||
checkClass(serviceName, superClassName, false /* testVisibility */);
|
||||
@@ -412,6 +456,9 @@ public class AndroidManifestParser {
|
||||
* the class or of its constructors.
|
||||
*/
|
||||
private void checkClass(String className, String superClassName, boolean testVisibility) {
|
||||
if (mJavaProject == null) {
|
||||
return;
|
||||
}
|
||||
// we need to check the validity of the activity.
|
||||
String result = BaseProjectHelper.testClassForManifest(mJavaProject,
|
||||
className, superClassName, testVisibility);
|
||||
@@ -477,6 +524,8 @@ public class AndroidManifestParser {
|
||||
private final String[] mProcesses;
|
||||
private final Boolean mDebuggable;
|
||||
private final int mApiLevelRequirement;
|
||||
private final String[] mInstrumentations;
|
||||
private final String[] mLibraries;
|
||||
|
||||
static {
|
||||
sParserFactory = SAXParserFactory.newInstance();
|
||||
@@ -484,8 +533,12 @@ public class AndroidManifestParser {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the Android Manifest, and returns an object containing
|
||||
* the result of the parsing.
|
||||
* Parses the Android Manifest, and returns an object containing the result of the parsing.
|
||||
* <p/>
|
||||
* This method is useful to parse a specific {@link IFile} in a Java project.
|
||||
* <p/>
|
||||
* If you only want to gather data, consider {@link #parseForData(IFile)} instead.
|
||||
*
|
||||
* @param javaProject The java project.
|
||||
* @param manifestFile the {@link IFile} representing the manifest file.
|
||||
* @param errorListener
|
||||
@@ -496,8 +549,12 @@ public class AndroidManifestParser {
|
||||
* @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)
|
||||
public static AndroidManifestParser parse(
|
||||
IJavaProject javaProject,
|
||||
IFile manifestFile,
|
||||
XmlErrorListener errorListener,
|
||||
boolean gatherData,
|
||||
boolean markErrors)
|
||||
throws CoreException {
|
||||
try {
|
||||
SAXParser parser = sParserFactory.newSAXParser();
|
||||
@@ -512,7 +569,51 @@ public class AndroidManifestParser {
|
||||
return new AndroidManifestParser(manifestHandler.getPackage(),
|
||||
manifestHandler.getActivities(), manifestHandler.getLauncherActivity(),
|
||||
manifestHandler.getProcesses(), manifestHandler.getDebuggable(),
|
||||
manifestHandler.getApiLevelRequirement());
|
||||
manifestHandler.getApiLevelRequirement(), manifestHandler.getInstrumentations(),
|
||||
manifestHandler.getUsesLibraries());
|
||||
} catch (ParserConfigurationException e) {
|
||||
} catch (SAXException e) {
|
||||
} catch (IOException e) {
|
||||
} finally {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the Android Manifest, and returns an object containing the result of the parsing.
|
||||
* <p/>
|
||||
* This version parses a real {@link File} file given by an actual path, which is useful for
|
||||
* parsing a file that is not part of an Eclipse Java project.
|
||||
* <p/>
|
||||
* It assumes errors cannot be marked on the file and that data gathering is enabled.
|
||||
*
|
||||
* @param manifestFile the manifest file to parse.
|
||||
* @return an {@link AndroidManifestParser} or null if the parsing failed.
|
||||
* @throws CoreException
|
||||
*/
|
||||
private static AndroidManifestParser parse(File manifestFile)
|
||||
throws CoreException {
|
||||
try {
|
||||
SAXParser parser = sParserFactory.newSAXParser();
|
||||
|
||||
ManifestHandler manifestHandler = new ManifestHandler(
|
||||
null, //manifestFile
|
||||
null, //errorListener
|
||||
true, //gatherData
|
||||
null, //javaProject
|
||||
false //markErrors
|
||||
);
|
||||
|
||||
parser.parse(new InputSource(new FileReader(manifestFile)), manifestHandler);
|
||||
|
||||
// get the result from the handler
|
||||
|
||||
return new AndroidManifestParser(manifestHandler.getPackage(),
|
||||
manifestHandler.getActivities(), manifestHandler.getLauncherActivity(),
|
||||
manifestHandler.getProcesses(), manifestHandler.getDebuggable(),
|
||||
manifestHandler.getApiLevelRequirement(), manifestHandler.getInstrumentations(),
|
||||
manifestHandler.getUsesLibraries());
|
||||
} catch (ParserConfigurationException e) {
|
||||
} catch (SAXException e) {
|
||||
} catch (IOException e) {
|
||||
@@ -535,13 +636,16 @@ public class AndroidManifestParser {
|
||||
* @return an {@link AndroidManifestParser} or null if the parsing failed.
|
||||
* @throws CoreException
|
||||
*/
|
||||
public static AndroidManifestParser parse(IJavaProject javaProject,
|
||||
XmlErrorListener errorListener, boolean gatherData, boolean markErrors)
|
||||
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());
|
||||
IFile manifestFile = getManifest(javaProject.getProject());
|
||||
if (manifestFile != null) {
|
||||
ManifestHandler manifestHandler = new ManifestHandler(manifestFile,
|
||||
errorListener, gatherData, javaProject, markErrors);
|
||||
@@ -552,7 +656,8 @@ public class AndroidManifestParser {
|
||||
return new AndroidManifestParser(manifestHandler.getPackage(),
|
||||
manifestHandler.getActivities(), manifestHandler.getLauncherActivity(),
|
||||
manifestHandler.getProcesses(), manifestHandler.getDebuggable(),
|
||||
manifestHandler.getApiLevelRequirement());
|
||||
manifestHandler.getApiLevelRequirement(),
|
||||
manifestHandler.getInstrumentations(), manifestHandler.getUsesLibraries());
|
||||
}
|
||||
} catch (ParserConfigurationException e) {
|
||||
} catch (SAXException e) {
|
||||
@@ -581,13 +686,30 @@ public class AndroidManifestParser {
|
||||
* Parses the manifest file, and collects data.
|
||||
* @param manifestFile The manifest file to parse.
|
||||
* @return an {@link AndroidManifestParser} or null if the parsing failed.
|
||||
* @throws CoreException
|
||||
* @throws CoreException for example the file does not exist in the workspace or
|
||||
* the workspace needs to be refreshed.
|
||||
*/
|
||||
public static AndroidManifestParser parseForData(IFile manifestFile) throws CoreException {
|
||||
return parse(null /* javaProject */, manifestFile, null /* errorListener */,
|
||||
true /* gatherData */, false /* markErrors */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the manifest file, and collects data.
|
||||
*
|
||||
* @param osManifestFilePath The OS path of the manifest file to parse.
|
||||
* @return an {@link AndroidManifestParser} or null if the parsing failed.
|
||||
*/
|
||||
public static AndroidManifestParser parseForData(String osManifestFilePath) {
|
||||
try {
|
||||
return parse(new File(osManifestFilePath));
|
||||
} catch (CoreException e) {
|
||||
// Ignore workspace errors (unlikely to happen since this parses an actual file,
|
||||
// not a workspace resource).
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the package defined in the manifest, if found.
|
||||
* @return The package name or null if not found.
|
||||
@@ -633,6 +755,22 @@ public class AndroidManifestParser {
|
||||
public int getApiLevelRequirement() {
|
||||
return mApiLevelRequirement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of instrumentations found in the manifest.
|
||||
* @return An array of fully qualified class names, or empty if no instrumentations were found.
|
||||
*/
|
||||
public String[] getInstrumentations() {
|
||||
return mInstrumentations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of libraries in use found in the manifest.
|
||||
* @return An array of library names, or empty if no uses-library declarations were found.
|
||||
*/
|
||||
public String[] getUsesLibraries() {
|
||||
return mLibraries;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -647,15 +785,92 @@ public class AndroidManifestParser {
|
||||
* @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.
|
||||
* @param instrumentations the list of instrumentations parsed from the manifest.
|
||||
* @param libraries the list of libraries in use parsed from the manifest.
|
||||
*/
|
||||
private AndroidManifestParser(String javaPackage, String[] activities,
|
||||
String launcherActivity, String[] processes, Boolean debuggable,
|
||||
int apiLevelRequirement) {
|
||||
int apiLevelRequirement, String[] instrumentations, String[] libraries) {
|
||||
mJavaPackage = javaPackage;
|
||||
mActivities = activities;
|
||||
mLauncherActivity = launcherActivity;
|
||||
mProcesses = processes;
|
||||
mDebuggable = debuggable;
|
||||
mApiLevelRequirement = apiLevelRequirement;
|
||||
mInstrumentations = instrumentations;
|
||||
mLibraries = libraries;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a fully qualified activity name (e.g. com.foo.test.MyClass) and given a project
|
||||
* package base name (e.g. com.foo), returns the relative activity name that would be used
|
||||
* the "name" attribute of an "activity" element.
|
||||
*
|
||||
* @param fullActivityName a fully qualified activity class name, e.g. "com.foo.test.MyClass"
|
||||
* @param packageName The project base package name, e.g. "com.foo"
|
||||
* @return The relative activity name if it can be computed or the original fullActivityName.
|
||||
*/
|
||||
public static String extractActivityName(String fullActivityName, String packageName) {
|
||||
if (packageName != null && fullActivityName != null) {
|
||||
if (packageName.length() > 0 && fullActivityName.startsWith(packageName)) {
|
||||
String name = fullActivityName.substring(packageName.length());
|
||||
if (name.length() > 0 && name.charAt(0) == '.') {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fullActivityName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,8 +86,13 @@ public class XmlErrorHandler extends DefaultHandler {
|
||||
*/
|
||||
@Override
|
||||
public void warning(SAXParseException exception) throws SAXException {
|
||||
BaseProjectHelper.addMarker(mFile, AndroidConstants.MARKER_XML, exception.getMessage(),
|
||||
exception.getLineNumber(), IMarker.SEVERITY_WARNING);
|
||||
if (mFile != null) {
|
||||
BaseProjectHelper.addMarker(mFile,
|
||||
AndroidConstants.MARKER_XML,
|
||||
exception.getMessage(),
|
||||
exception.getLineNumber(),
|
||||
IMarker.SEVERITY_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
protected final IFile getFile() {
|
||||
@@ -104,12 +109,19 @@ public class XmlErrorHandler extends DefaultHandler {
|
||||
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);
|
||||
if (mFile != null) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,6 +313,11 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
|
||||
if (currentUiNode != null) {
|
||||
// look for an UI attribute matching the current attribute name
|
||||
String attrName = attrInfo.name;
|
||||
// remove any namespace prefix from the attribute name
|
||||
int pos = attrName.indexOf(':');
|
||||
if (pos >= 0) {
|
||||
attrName = attrName.substring(pos + 1);
|
||||
}
|
||||
|
||||
UiAttributeNode currAttrNode = null;
|
||||
for (UiAttributeNode attrNode : currentUiNode.getUiAttributes()) {
|
||||
@@ -323,13 +328,13 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
|
||||
}
|
||||
|
||||
if (currAttrNode != null) {
|
||||
choices = currAttrNode.getPossibleValues();
|
||||
choices = currAttrNode.getPossibleValues(value);
|
||||
|
||||
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('|');
|
||||
pos = value.indexOf('|');
|
||||
if (pos >= 0) {
|
||||
attrInfo.correctedPrefix = value = value.substring(pos + 1);
|
||||
attrInfo.needTag = 0;
|
||||
@@ -437,11 +442,15 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
|
||||
tooltip = ((TextAttributeDescriptor) choice).getTooltip();
|
||||
}
|
||||
|
||||
// Get the namespace URI for the attribute. Note that some attributes
|
||||
// do not have a namespace and thus return null here.
|
||||
String nsUri = ((AttributeDescriptor)choice).getNamespaceUri();
|
||||
nsPrefix = nsUriMap.get(nsUri);
|
||||
if (nsPrefix == null) {
|
||||
nsPrefix = lookupNamespacePrefix(currentNode, nsUri);
|
||||
nsUriMap.put(nsUri, nsPrefix);
|
||||
if (nsUri != null) {
|
||||
nsPrefix = nsUriMap.get(nsUri);
|
||||
if (nsPrefix == null) {
|
||||
nsPrefix = lookupNamespacePrefix(currentNode, nsUri);
|
||||
nsUriMap.put(nsUri, nsPrefix);
|
||||
}
|
||||
}
|
||||
if (nsPrefix != null) {
|
||||
nsPrefix += ":"; //$NON-NLS-1$
|
||||
@@ -452,9 +461,12 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
|
||||
} else {
|
||||
continue; // discard unknown choice
|
||||
}
|
||||
|
||||
String nsKeyword = nsPrefix == null ? keyword : (nsPrefix + keyword);
|
||||
|
||||
if (keyword.startsWith(wordPrefix) ||
|
||||
(nsPrefix != null && keyword.startsWith(nsPrefix))) {
|
||||
(nsPrefix != null && keyword.startsWith(nsPrefix)) ||
|
||||
(nsPrefix != null && nsKeyword.startsWith(wordPrefix))) {
|
||||
if (nsPrefix != null) {
|
||||
keyword = nsPrefix + keyword;
|
||||
}
|
||||
@@ -561,10 +573,11 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
|
||||
/**
|
||||
* 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.
|
||||
* offset backwards that forms a potential XML element name, attribute name or
|
||||
* attribute value.
|
||||
*
|
||||
* Code extracted from org.eclipse.jface.text.templatesTemplateCompletionProcessor
|
||||
* and adapted to our needs.
|
||||
* The part were we access the docment was extracted from
|
||||
* org.eclipse.jface.text.templatesTemplateCompletionProcessor and adapted to our needs.
|
||||
*
|
||||
* @param viewer the viewer
|
||||
* @param offset offset into document
|
||||
@@ -578,8 +591,19 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
|
||||
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;
|
||||
|
||||
// We want all characters that can form a valid:
|
||||
// - element name, e.g. anything that is a valid Java class/variable literal.
|
||||
// - attribute name, including : for the namespace
|
||||
// - attribute value.
|
||||
// Before we were inclusive and that made the code fragile. So now we're
|
||||
// going to be exclusive: take everything till we get one of:
|
||||
// - any form of whitespace
|
||||
// - any xml separator, e.g. < > ' " and =
|
||||
if (Character.isWhitespace(ch) ||
|
||||
ch == '<' || ch == '>' || ch == '\'' || ch == '"' || ch == '=') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return document.get(i, offset - i);
|
||||
|
||||
@@ -19,6 +19,7 @@ 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.adt.sdk.Sdk.ITargetChangeListener;
|
||||
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
|
||||
import com.android.sdklib.IAndroidTarget;
|
||||
|
||||
@@ -97,8 +98,9 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
|
||||
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;
|
||||
/** Listener to update the root node if the target of the file is changed because of a
|
||||
* SDK location change or a project target change */
|
||||
private ITargetChangeListener mTargetListener;
|
||||
|
||||
/**
|
||||
* Creates a form editor.
|
||||
@@ -107,15 +109,21 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
|
||||
super();
|
||||
ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
|
||||
|
||||
mResourceRefreshListener = new Runnable() {
|
||||
public void run() {
|
||||
commitPages(false /* onSave */);
|
||||
mTargetListener = new ITargetChangeListener() {
|
||||
public void onProjectTargetChange(IProject changedProject) {
|
||||
if (changedProject == getProject()) {
|
||||
onTargetsLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
public void onTargetsLoaded() {
|
||||
commitPages(false /* onSave */);
|
||||
|
||||
// recreate the ui root node always
|
||||
initUiRootNode(true /*force*/);
|
||||
}
|
||||
};
|
||||
AdtPlugin.getDefault().addResourceChangedListener(mResourceRefreshListener);
|
||||
AdtPlugin.getDefault().addTargetListener(mTargetListener);
|
||||
}
|
||||
|
||||
// ---- Abstract Methods ----
|
||||
@@ -340,9 +348,9 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
|
||||
}
|
||||
ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
|
||||
|
||||
if (mResourceRefreshListener != null) {
|
||||
AdtPlugin.getDefault().removeResourceChangedListener(mResourceRefreshListener);
|
||||
mResourceRefreshListener = null;
|
||||
if (mTargetListener != null) {
|
||||
AdtPlugin.getDefault().removeTargetListener(mTargetListener);
|
||||
mTargetListener = null;
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
package com.android.ide.eclipse.editors;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.sdklib.SdkConstants;
|
||||
|
||||
import org.eclipse.jface.resource.ImageDescriptor;
|
||||
import org.eclipse.swt.SWT;
|
||||
@@ -231,7 +231,7 @@ public class IconFactory {
|
||||
// Text measurement varies so slightly depending on the platform
|
||||
int ofx = 0;
|
||||
int ofy = 0;
|
||||
if (AndroidConstants.CURRENT_PLATFORM == AndroidConstants.PLATFORM_WINDOWS) {
|
||||
if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
|
||||
ofx = +1;
|
||||
ofy = -1;
|
||||
}
|
||||
|
||||
@@ -313,7 +313,7 @@ public final class DescriptorsUtils {
|
||||
*
|
||||
* @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.
|
||||
* See {@link SdkConstants#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.
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.editors.layout;
|
||||
|
||||
import com.android.ide.eclipse.editors.layout.LayoutReloadMonitor.ILayoutReloadListener;
|
||||
import com.android.ide.eclipse.editors.layout.parts.ElementCreateCommand;
|
||||
import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration;
|
||||
import com.android.ide.eclipse.editors.uimodel.UiDocumentNode;
|
||||
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
|
||||
|
||||
import org.eclipse.gef.DefaultEditDomain;
|
||||
import org.eclipse.gef.ui.parts.GraphicalEditorWithPalette;
|
||||
import org.eclipse.gef.ui.parts.SelectionSynchronizer;
|
||||
import org.eclipse.swt.dnd.Clipboard;
|
||||
import org.eclipse.ui.IWorkbenchPart;
|
||||
|
||||
/**
|
||||
* Abstract GraphicalLayoutEditor.
|
||||
*/
|
||||
/*package*/ abstract class AbstractGraphicalLayoutEditor extends GraphicalEditorWithPalette
|
||||
implements IWorkbenchPart, ILayoutReloadListener {
|
||||
|
||||
/**
|
||||
* Sets the UI for the edition of a new file.
|
||||
* @param configuration the configuration of the new file.
|
||||
*/
|
||||
abstract void editNewFile(FolderConfiguration configuration);
|
||||
|
||||
/**
|
||||
* Reloads this editor, by getting the new model from the {@link LayoutEditor}.
|
||||
*/
|
||||
abstract void reloadEditor();
|
||||
|
||||
/**
|
||||
* Callback for XML model changed. Only update/recompute the layout if the editor is visible
|
||||
*/
|
||||
abstract void onXmlModelChanged();
|
||||
|
||||
/**
|
||||
* Responds to a page change that made the Graphical editor page the activated page.
|
||||
*/
|
||||
abstract void activated();
|
||||
|
||||
/**
|
||||
* Responds to a page change that made the Graphical editor page the deactivated page
|
||||
*/
|
||||
abstract void deactivated();
|
||||
|
||||
/**
|
||||
* Used by LayoutEditor.UiEditorActions.selectUiNode to select a new UI Node
|
||||
* created by {@link ElementCreateCommand#execute()}.
|
||||
*
|
||||
* @param uiNodeModel The {@link UiElementNode} to select.
|
||||
*/
|
||||
abstract void selectModel(UiElementNode uiNodeModel);
|
||||
|
||||
/**
|
||||
* Returns the selection synchronizer object.
|
||||
* The synchronizer can be used to sync the selection of 2 or more EditPartViewers.
|
||||
* <p/>
|
||||
* This is changed from protected to public so that the outline can use it.
|
||||
*
|
||||
* @return the synchronizer
|
||||
*/
|
||||
@Override
|
||||
public SelectionSynchronizer getSelectionSynchronizer() {
|
||||
return super.getSelectionSynchronizer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the edit domain.
|
||||
* <p/>
|
||||
* This is changed from protected to public so that the outline can use it.
|
||||
*
|
||||
* @return the edit domain
|
||||
*/
|
||||
@Override
|
||||
public DefaultEditDomain getEditDomain() {
|
||||
return super.getEditDomain();
|
||||
}
|
||||
|
||||
abstract void reloadPalette();
|
||||
|
||||
abstract void recomputeLayout();
|
||||
|
||||
abstract UiDocumentNode getModel();
|
||||
|
||||
abstract LayoutEditor getLayoutEditor();
|
||||
|
||||
abstract Clipboard getClipboard();
|
||||
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import com.android.ide.eclipse.adt.sdk.AndroidTargetData;
|
||||
import com.android.ide.eclipse.adt.sdk.LoadStatus;
|
||||
import com.android.ide.eclipse.adt.sdk.Sdk;
|
||||
import com.android.ide.eclipse.adt.sdk.AndroidTargetData.LayoutBridge;
|
||||
import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener;
|
||||
import com.android.ide.eclipse.common.resources.ResourceType;
|
||||
import com.android.ide.eclipse.editors.IconFactory;
|
||||
import com.android.ide.eclipse.editors.layout.LayoutEditor.UiEditorActions;
|
||||
@@ -85,8 +86,6 @@ import org.eclipse.gef.dnd.TemplateTransferDropTargetListener;
|
||||
import org.eclipse.gef.editparts.ScalableFreeformRootEditPart;
|
||||
import org.eclipse.gef.palette.PaletteRoot;
|
||||
import org.eclipse.gef.requests.CreationFactory;
|
||||
import org.eclipse.gef.ui.parts.GraphicalEditorWithPalette;
|
||||
import org.eclipse.gef.ui.parts.SelectionSynchronizer;
|
||||
import org.eclipse.jface.action.Action;
|
||||
import org.eclipse.jface.action.IMenuListener;
|
||||
import org.eclipse.jface.action.IMenuManager;
|
||||
@@ -140,7 +139,7 @@ import java.util.Set;
|
||||
* <p/>
|
||||
* To understand Drag'n'drop: http://www.eclipse.org/articles/Article-Workbench-DND/drag_drop.html
|
||||
*/
|
||||
public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
|
||||
public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
|
||||
implements ILayoutReloadListener {
|
||||
|
||||
private final static String THEME_SEPARATOR = "----------"; //$NON-NLS-1$
|
||||
@@ -198,13 +197,21 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
|
||||
private ProjectCallback mProjectCallback;
|
||||
private ILayoutLog mLogger;
|
||||
|
||||
private boolean mNeedsXmlReload = false;
|
||||
private boolean mNeedsRecompute = false;
|
||||
private int mPlatformThemeCount = 0;
|
||||
private boolean mDisableUpdates = false;
|
||||
private boolean mActive = false;
|
||||
|
||||
private Runnable mFrameworkResourceChangeListener = new Runnable() {
|
||||
public void run() {
|
||||
/** Listener to update the root node if the target of the file is changed because of a
|
||||
* SDK location change or a project target change */
|
||||
private ITargetChangeListener mTargetListener = new ITargetChangeListener() {
|
||||
public void onProjectTargetChange(IProject changedProject) {
|
||||
if (changedProject == getLayoutEditor().getProject()) {
|
||||
onTargetsLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
public void onTargetsLoaded() {
|
||||
// because the SDK changed we must reset the configured framework resource.
|
||||
mConfiguredFrameworkRes = null;
|
||||
|
||||
@@ -228,7 +235,7 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
|
||||
|
||||
private final Runnable mConditionalRecomputeRunnable = new Runnable() {
|
||||
public void run() {
|
||||
if (mActive) {
|
||||
if (mLayoutEditor.isGraphicalEditorActive()) {
|
||||
recomputeLayout();
|
||||
} else {
|
||||
mNeedsRecompute = true;
|
||||
@@ -253,7 +260,7 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
|
||||
mMatchImage = factory.getIcon("match"); //$NON-NLS-1$
|
||||
mErrorImage = factory.getIcon("error"); //$NON-NLS-1$
|
||||
|
||||
AdtPlugin.getDefault().addResourceChangedListener(mFrameworkResourceChangeListener);
|
||||
AdtPlugin.getDefault().addTargetListener(mTargetListener);
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
@@ -561,10 +568,9 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (mFrameworkResourceChangeListener != null) {
|
||||
AdtPlugin.getDefault().removeResourceChangedListener(
|
||||
mFrameworkResourceChangeListener);
|
||||
mFrameworkResourceChangeListener = null;
|
||||
if (mTargetListener != null) {
|
||||
AdtPlugin.getDefault().removeTargetListener(mTargetListener);
|
||||
mTargetListener = null;
|
||||
}
|
||||
|
||||
LayoutReloadMonitor.getMonitor().removeListener(mEditedFile.getProject(), this);
|
||||
@@ -587,6 +593,7 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
|
||||
return mPaletteRoot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Clipboard getClipboard() {
|
||||
return mClipboard;
|
||||
}
|
||||
@@ -708,7 +715,8 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
|
||||
*
|
||||
* @param uiNodeModel The {@link UiElementNode} to select.
|
||||
*/
|
||||
public void selectModel(UiElementNode uiNodeModel) {
|
||||
@Override
|
||||
void selectModel(UiElementNode uiNodeModel) {
|
||||
GraphicalViewer viewer = getGraphicalViewer();
|
||||
|
||||
// Give focus to the graphical viewer (in case the outline has it)
|
||||
@@ -726,6 +734,7 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
|
||||
// Local methods
|
||||
//--------------
|
||||
|
||||
@Override
|
||||
public LayoutEditor getLayoutEditor() {
|
||||
return mLayoutEditor;
|
||||
}
|
||||
@@ -855,7 +864,8 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
|
||||
* Sets the UI for the edition of a new file.
|
||||
* @param configuration the configuration of the new file.
|
||||
*/
|
||||
public void editNewFile(FolderConfiguration configuration) {
|
||||
@Override
|
||||
void editNewFile(FolderConfiguration configuration) {
|
||||
// update the configuration UI
|
||||
setConfiguration(configuration);
|
||||
|
||||
@@ -1007,6 +1017,7 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
|
||||
/**
|
||||
* Reloads this editor, by getting the new model from the {@link LayoutEditor}.
|
||||
*/
|
||||
@Override
|
||||
void reloadEditor() {
|
||||
GraphicalViewer viewer = getGraphicalViewer();
|
||||
viewer.setContents(getModel());
|
||||
@@ -1026,25 +1037,37 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the layout editor when the Xml model is changed.
|
||||
* Callback for XML model changed. Only update/recompute the layout if the editor is visible
|
||||
*/
|
||||
@Override
|
||||
void onXmlModelChanged() {
|
||||
GraphicalViewer viewer = getGraphicalViewer();
|
||||
|
||||
// try to preserve the selection before changing the content
|
||||
SelectionManager selMan = viewer.getSelectionManager();
|
||||
ISelection selection = selMan.getSelection();
|
||||
|
||||
try {
|
||||
viewer.setContents(getModel());
|
||||
} finally {
|
||||
selMan.setSelection(selection);
|
||||
}
|
||||
|
||||
if (mLayoutEditor.isGraphicalEditorActive()) {
|
||||
doXmlReload(true /* force */);
|
||||
recomputeLayout();
|
||||
} else {
|
||||
mNeedsRecompute = true;
|
||||
mNeedsXmlReload = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually performs the XML reload
|
||||
* @see #onXmlModelChanged()
|
||||
*/
|
||||
private void doXmlReload(boolean force) {
|
||||
if (force || mNeedsXmlReload) {
|
||||
GraphicalViewer viewer = getGraphicalViewer();
|
||||
|
||||
// try to preserve the selection before changing the content
|
||||
SelectionManager selMan = viewer.getSelectionManager();
|
||||
ISelection selection = selMan.getSelection();
|
||||
|
||||
try {
|
||||
viewer.setContents(getModel());
|
||||
} finally {
|
||||
selMan.setSelection(selection);
|
||||
}
|
||||
|
||||
mNeedsXmlReload = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1246,10 +1269,12 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
|
||||
mCurrentLayoutLabel.setText(current != null ? current : "(Default)");
|
||||
}
|
||||
|
||||
@Override
|
||||
UiDocumentNode getModel() {
|
||||
return mLayoutEditor.getUiRootNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
void reloadPalette() {
|
||||
PaletteFactory.createPaletteRoot(mPaletteRoot, mLayoutEditor.getTargetData());
|
||||
}
|
||||
@@ -1648,7 +1673,10 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
|
||||
/**
|
||||
* Recomputes the layout with the help of layoutlib.
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
void recomputeLayout() {
|
||||
doXmlReload(false /* force */);
|
||||
try {
|
||||
// check that the resource exists. If the file is opened but the project is closed
|
||||
// or deleted for some reason (changed from outside of eclipse), then this will
|
||||
@@ -1691,7 +1719,7 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
|
||||
// In this case data could be null, but this is not an error.
|
||||
// We can just silently return, as all the opened editors are automatically
|
||||
// refreshed once the SDK finishes loading.
|
||||
if (AdtPlugin.getDefault().getSdkLoadStatus(null) != LoadStatus.LOADING) {
|
||||
if (AdtPlugin.getDefault().getSdkLoadStatus() != LoadStatus.LOADING) {
|
||||
showErrorInEditor(String.format(
|
||||
"The project target (%s) was not properly loaded.",
|
||||
target.getName()));
|
||||
@@ -1763,20 +1791,47 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
|
||||
if (themeIndex != -1) {
|
||||
String theme = mThemeCombo.getItem(themeIndex);
|
||||
|
||||
// change the string if it's a custom theme to make sure we can
|
||||
// differentiate them
|
||||
if (themeIndex >= mPlatformThemeCount) {
|
||||
theme = "*" + theme; //$NON-NLS-1$
|
||||
}
|
||||
|
||||
// Compute the layout
|
||||
UiElementPullParser parser = new UiElementPullParser(getModel());
|
||||
Rectangle rect = getBounds();
|
||||
ILayoutResult result = bridge.bridge.computeLayout(parser,
|
||||
iProject /* projectKey */,
|
||||
rect.width, rect.height, theme,
|
||||
mConfiguredProjectRes, frameworkResources, mProjectCallback,
|
||||
mLogger);
|
||||
ILayoutResult result = null;
|
||||
if (bridge.apiLevel >= 3) {
|
||||
// call the new api with proper theme differentiator and
|
||||
// density/dpi support.
|
||||
boolean isProjectTheme = themeIndex >= mPlatformThemeCount;
|
||||
|
||||
// FIXME pass the density/dpi from somewhere (resource config or skin).
|
||||
result = bridge.bridge.computeLayout(parser,
|
||||
iProject /* projectKey */,
|
||||
rect.width, rect.height, 160, 160.f, 160.f,
|
||||
theme, isProjectTheme,
|
||||
mConfiguredProjectRes, frameworkResources, mProjectCallback,
|
||||
mLogger);
|
||||
} else if (bridge.apiLevel == 2) {
|
||||
// api with boolean for separation of project/framework theme
|
||||
boolean isProjectTheme = themeIndex >= mPlatformThemeCount;
|
||||
|
||||
result = bridge.bridge.computeLayout(parser,
|
||||
iProject /* projectKey */,
|
||||
rect.width, rect.height, theme, isProjectTheme,
|
||||
mConfiguredProjectRes, frameworkResources, mProjectCallback,
|
||||
mLogger);
|
||||
} else {
|
||||
// oldest api with no density/dpi, and project theme boolean mixed
|
||||
// into the theme name.
|
||||
|
||||
// change the string if it's a custom theme to make sure we can
|
||||
// differentiate them
|
||||
if (themeIndex >= mPlatformThemeCount) {
|
||||
theme = "*" + theme; //$NON-NLS-1$
|
||||
}
|
||||
|
||||
result = bridge.bridge.computeLayout(parser,
|
||||
iProject /* projectKey */,
|
||||
rect.width, rect.height, theme,
|
||||
mConfiguredProjectRes, frameworkResources, mProjectCallback,
|
||||
mLogger);
|
||||
}
|
||||
|
||||
// update the UiElementNode with the layout info.
|
||||
if (result.getSuccess() == ILayoutResult.SUCCESS) {
|
||||
@@ -1920,9 +1975,9 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
|
||||
/**
|
||||
* Responds to a page change that made the Graphical editor page the activated page.
|
||||
*/
|
||||
@Override
|
||||
void activated() {
|
||||
mActive = true;
|
||||
if (mNeedsRecompute) {
|
||||
if (mNeedsRecompute || mNeedsXmlReload) {
|
||||
recomputeLayout();
|
||||
}
|
||||
}
|
||||
@@ -1930,8 +1985,9 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
|
||||
/**
|
||||
* Responds to a page change that made the Graphical editor page the deactivated page
|
||||
*/
|
||||
@Override
|
||||
void deactivated() {
|
||||
mActive = false;
|
||||
// nothing to be done here for now.
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2186,31 +2242,6 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette
|
||||
return mConfiguredFrameworkRes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the selection synchronizer object.
|
||||
* The synchronizer can be used to sync the selection of 2 or more EditPartViewers.
|
||||
* <p/>
|
||||
* This is changed from protected to public so that the outline can use it.
|
||||
*
|
||||
* @return the synchronizer
|
||||
*/
|
||||
@Override
|
||||
public SelectionSynchronizer getSelectionSynchronizer() {
|
||||
return super.getSelectionSynchronizer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the edit domain.
|
||||
* <p/>
|
||||
* This is changed from protected to public so that the outline can use it.
|
||||
*
|
||||
* @return the edit domain
|
||||
*/
|
||||
@Override
|
||||
public DefaultEditDomain getEditDomain() {
|
||||
return super.getEditDomain();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new layout file from the specificed {@link FolderConfiguration}.
|
||||
*/
|
||||
|
||||
@@ -55,7 +55,7 @@ public class LayoutEditor extends AndroidEditor implements IShowEditorInput, IPa
|
||||
/** Root node of the UI element hierarchy */
|
||||
private UiDocumentNode mUiRootNode;
|
||||
|
||||
private GraphicalLayoutEditor mGraphicalEditor;
|
||||
private AbstractGraphicalLayoutEditor mGraphicalEditor;
|
||||
private int mGraphicalEditorIndex;
|
||||
/** Implementation of the {@link IContentOutlinePage} for this editor */
|
||||
private UiContentOutlinePage mOutline;
|
||||
@@ -269,8 +269,12 @@ public class LayoutEditor extends AndroidEditor implements IShowEditorInput, IPa
|
||||
protected void pageChange(int newPageIndex) {
|
||||
super.pageChange(newPageIndex);
|
||||
|
||||
if (mGraphicalEditor != null && newPageIndex == mGraphicalEditorIndex) {
|
||||
mGraphicalEditor.activated();
|
||||
if (mGraphicalEditor != null) {
|
||||
if (newPageIndex == mGraphicalEditorIndex) {
|
||||
mGraphicalEditor.activated();
|
||||
} else {
|
||||
mGraphicalEditor.deactivated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,8 +282,12 @@ public class LayoutEditor extends AndroidEditor implements IShowEditorInput, IPa
|
||||
|
||||
public void partActivated(IWorkbenchPart part) {
|
||||
if (part == this) {
|
||||
if (mGraphicalEditor != null && getActivePage() == mGraphicalEditorIndex) {
|
||||
mGraphicalEditor.activated();
|
||||
if (mGraphicalEditor != null) {
|
||||
if (getActivePage() == mGraphicalEditorIndex) {
|
||||
mGraphicalEditor.activated();
|
||||
} else {
|
||||
mGraphicalEditor.deactivated();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,23 +342,23 @@ public class LayoutEditor extends AndroidEditor implements IShowEditorInput, IPa
|
||||
// ---- Local Methods ----
|
||||
|
||||
/**
|
||||
* Returns true if the Graphics editor page is visible.
|
||||
* This <b>must</b> be called from the UI thread.
|
||||
* Returns true if the Graphics editor page is visible. This <b>must</b> be
|
||||
* called from the UI thread.
|
||||
*/
|
||||
boolean isGraphicalEditorActive() {
|
||||
IWorkbenchPartSite workbenchSite = getSite();
|
||||
IWorkbenchPage workbenchPage = workbenchSite.getPage();
|
||||
|
||||
|
||||
// check if the editor is visible in the workbench page
|
||||
if (workbenchPage.isPartVisible(this)) {
|
||||
if (workbenchPage.isPartVisible(this) && workbenchPage.getActiveEditor() == this) {
|
||||
// and then if the page of the editor is visible (not to be confused with
|
||||
// the workbench page)
|
||||
return mGraphicalEditorIndex == getActivePage();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initUiRootNode(boolean force) {
|
||||
// The root UI node is always created, even if there's no corresponding XML node.
|
||||
|
||||
@@ -18,12 +18,14 @@ package com.android.ide.eclipse.editors.layout;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
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.editors.resources.manager.ProjectClassLoader;
|
||||
import com.android.ide.eclipse.editors.resources.manager.ProjectResources;
|
||||
import com.android.layoutlib.api.IProjectCallback;
|
||||
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.HashMap;
|
||||
@@ -83,17 +85,20 @@ public final class ProjectCallback implements IProjectCallback {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Returns the namespace for the project. The namespace contains a standard part + the
|
||||
* application package.
|
||||
*
|
||||
* @return The package namespace of the project or null in case of error.
|
||||
*/
|
||||
public String getNamespace() {
|
||||
if (mNamespace == null) {
|
||||
AndroidManifestHelper manifest = new AndroidManifestHelper(mProject);
|
||||
String javaPackage = manifest.getPackageName();
|
||||
|
||||
mNamespace = String.format(AndroidConstants.NS_CUSTOM_RESOURCES, javaPackage);
|
||||
IFile manifestFile = AndroidManifestParser.getManifest(mProject);
|
||||
try {
|
||||
AndroidManifestParser data = AndroidManifestParser.parseForData(manifestFile);
|
||||
String javaPackage = data.getPackage();
|
||||
mNamespace = String.format(AndroidConstants.NS_CUSTOM_RESOURCES, javaPackage);
|
||||
} catch (CoreException e) {
|
||||
}
|
||||
}
|
||||
|
||||
return mNamespace;
|
||||
|
||||
@@ -70,7 +70,7 @@ import java.util.List;
|
||||
*/
|
||||
class UiContentOutlinePage extends ContentOutlinePage {
|
||||
|
||||
private GraphicalLayoutEditor mEditor;
|
||||
private AbstractGraphicalLayoutEditor mEditor;
|
||||
|
||||
private Action mAddAction;
|
||||
private Action mDeleteAction;
|
||||
@@ -79,7 +79,7 @@ class UiContentOutlinePage extends ContentOutlinePage {
|
||||
|
||||
private UiOutlineActions mUiActions = new UiOutlineActions();
|
||||
|
||||
public UiContentOutlinePage(GraphicalLayoutEditor editor, final EditPartViewer viewer) {
|
||||
public UiContentOutlinePage(AbstractGraphicalLayoutEditor editor, final EditPartViewer viewer) {
|
||||
super(viewer);
|
||||
mEditor = editor;
|
||||
IconFactory factory = IconFactory.getInstance();
|
||||
|
||||
@@ -187,10 +187,10 @@ public final class CustomViewDescriptorService {
|
||||
/**
|
||||
* Computes (if needed) and returns the {@link ElementDescriptor} for the specified type.
|
||||
*
|
||||
* @param type
|
||||
* @param type
|
||||
* @param project
|
||||
* @param typeHierarchy
|
||||
* @return A ViewElementDescriptor
|
||||
* @return A ViewElementDescriptor or null if type or typeHierarchy is null.
|
||||
*/
|
||||
private ViewElementDescriptor getDescriptor(IType type, IProject project,
|
||||
ITypeHierarchy typeHierarchy) {
|
||||
@@ -198,12 +198,17 @@ public final class CustomViewDescriptorService {
|
||||
List<ElementDescriptor> builtInList = null;
|
||||
|
||||
Sdk currentSdk = Sdk.getCurrent();
|
||||
IAndroidTarget target = currentSdk.getTarget(project);
|
||||
IAndroidTarget target = currentSdk == null ? null : currentSdk.getTarget(project);
|
||||
if (target != null) {
|
||||
AndroidTargetData data = currentSdk.getTargetData(target);
|
||||
builtInList = data.getLayoutDescriptors().getViewDescriptors();
|
||||
}
|
||||
|
||||
// give up if there's no type
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String canonicalName = type.getFullyQualifiedName();
|
||||
|
||||
if (builtInList != null) {
|
||||
@@ -218,6 +223,11 @@ public final class CustomViewDescriptorService {
|
||||
}
|
||||
|
||||
// it's not a built-in class? Lets look if the superclass is built-in
|
||||
// give up if there's no type
|
||||
if (typeHierarchy == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
IType parentType = typeHierarchy.getSuperclass(type);
|
||||
if (parentType != null) {
|
||||
ViewElementDescriptor parentDescriptor = getDescriptor(parentType, project,
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.ide.eclipse.editors.layout.descriptors;
|
||||
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.common.resources.DeclareStyleableInfo;
|
||||
import com.android.ide.eclipse.common.resources.ViewClassInfo;
|
||||
import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo;
|
||||
import com.android.ide.eclipse.common.resources.ViewClassInfo.LayoutParamsInfo;
|
||||
@@ -131,8 +132,23 @@ public final class LayoutDescriptors implements IDescriptorProvider {
|
||||
String xml_name = info.getShortClassName();
|
||||
String tooltip = info.getJavaDoc();
|
||||
|
||||
// Process all View attributes
|
||||
ArrayList<AttributeDescriptor> attributes = new ArrayList<AttributeDescriptor>();
|
||||
|
||||
// All views and groups have an implicit "style" attribute which is a reference.
|
||||
AttributeInfo styleInfo = new DeclareStyleableInfo.AttributeInfo(
|
||||
"style", //$NON-NLS-1$ xmlLocalName
|
||||
new DeclareStyleableInfo.AttributeInfo.Format[] {
|
||||
DeclareStyleableInfo.AttributeInfo.Format.REFERENCE
|
||||
});
|
||||
styleInfo.setJavaDoc("A reference to a custom style"); //tooltip
|
||||
DescriptorsUtils.appendAttribute(attributes,
|
||||
"style", //$NON-NLS-1$
|
||||
null, //nsUri
|
||||
styleInfo,
|
||||
false, //required
|
||||
null); // overrides
|
||||
|
||||
// Process all View attributes
|
||||
DescriptorsUtils.appendAttributes(attributes,
|
||||
null, // elementName
|
||||
SdkConstants.NS_RESOURCES,
|
||||
@@ -155,7 +171,7 @@ public final class LayoutDescriptors implements IDescriptorProvider {
|
||||
null /* overrides */);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Process all LayoutParams attributes
|
||||
ArrayList<AttributeDescriptor> layoutAttributes = new ArrayList<AttributeDescriptor>();
|
||||
LayoutParamsInfo layoutParams = info.getLayoutData();
|
||||
|
||||
@@ -195,6 +195,8 @@ public final class AndroidManifestDescriptors implements IDescriptorProvider {
|
||||
overrides.put("*/permission", ListAttributeDescriptor.class); //$NON-NLS-1$
|
||||
overrides.put("*/targetPackage", PackageAttributeDescriptor.class); //$NON-NLS-1$
|
||||
|
||||
overrides.put("uses-library/name", ListAttributeDescriptor.class); //$NON-NLS-1$
|
||||
|
||||
overrides.put("action,category,uses-permission/" + ANDROID_NAME_ATTR, //$NON-NLS-1$
|
||||
ListAttributeDescriptor.class);
|
||||
overrides.put("application/" + ANDROID_NAME_ATTR, ApplicationAttributeDescriptor.class); //$NON-NLS-1$
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
|
||||
package com.android.ide.eclipse.editors.manifest.descriptors;
|
||||
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor;
|
||||
import com.android.ide.eclipse.editors.manifest.model.UiClassAttributeNode;
|
||||
import com.android.ide.eclipse.editors.manifest.model.UiClassAttributeNode.IPostTypeCreationAction;
|
||||
import com.android.ide.eclipse.editors.uimodel.UiAttributeNode;
|
||||
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
|
||||
import com.android.sdklib.SdkConstants;
|
||||
|
||||
/**
|
||||
* Describes an XML attribute representing a class name.
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
package com.android.ide.eclipse.editors.manifest.model;
|
||||
|
||||
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.editors.AndroidEditor;
|
||||
import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor;
|
||||
@@ -251,8 +251,8 @@ public class UiClassAttributeNode extends UiTextAttributeNode {
|
||||
String javaPackage = getManifestPackage();
|
||||
|
||||
// build the fully qualified name of the class
|
||||
String className = AndroidManifestHelper.combinePackageAndClassName(javaPackage,
|
||||
textValue);
|
||||
String className = AndroidManifestParser.combinePackageAndClassName(
|
||||
javaPackage, textValue);
|
||||
|
||||
// only test the vilibility for activities.
|
||||
boolean testVisibility = AndroidConstants.CLASS_ACTIVITY.equals(
|
||||
@@ -616,7 +616,7 @@ public class UiClassAttributeNode extends UiTextAttributeNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getPossibleValues() {
|
||||
public String[] getPossibleValues(String prefix) {
|
||||
// TODO: compute a list of existing classes for content assist completion
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -311,7 +311,7 @@ public class UiPackageAttributeNode extends UiTextAttributeNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getPossibleValues() {
|
||||
public String[] getPossibleValues(String prefix) {
|
||||
// TODO: compute a list of existing packages for content assist completion
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -160,6 +160,13 @@ public class ResourceExplorerView extends ViewPart implements ISelectionListener
|
||||
// set up the resource manager to send us resource change notification
|
||||
AdtPlugin.getDefault().getResourceMonitor().addResourceEventListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
AdtPlugin.getDefault().getResourceMonitor().removeResourceEventListener(this);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFocus() {
|
||||
|
||||
@@ -18,7 +18,7 @@ package com.android.ide.eclipse.editors.resources.manager;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
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.resources.ResourceType;
|
||||
import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFileListener;
|
||||
import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IProjectListener;
|
||||
@@ -28,6 +28,7 @@ import org.eclipse.core.resources.IMarkerDelta;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IResourceDelta;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -120,7 +121,14 @@ public final class CompiledResourcesMonitor implements IFileListener, IProjectLi
|
||||
if (projectResources != null) {
|
||||
// create the classname
|
||||
String className = getRClassName(project);
|
||||
|
||||
if (className == null) {
|
||||
// We need to abort.
|
||||
AdtPlugin.log(IStatus.ERROR,
|
||||
"loadAndParseRClass: failed to find manifest package for project %1$s", //$NON-NLS-1$
|
||||
project.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
// create a temporary class loader to load it.
|
||||
ProjectClassLoader loader = new ProjectClassLoader(null /* parentClassLoader */,
|
||||
project);
|
||||
@@ -199,13 +207,30 @@ public final class CompiledResourcesMonitor implements IFileListener, IProjectLi
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the class name of the R class, based on the project's manifest's package.
|
||||
*
|
||||
* @return A class name (e.g. "my.app.R") or null if there's no valid package in the manifest.
|
||||
*/
|
||||
private String getRClassName(IProject project) {
|
||||
// create the classname
|
||||
AndroidManifestHelper manifest = new AndroidManifestHelper(project);
|
||||
String javaPackage = manifest.getPackageName();
|
||||
|
||||
return javaPackage + ".R"; //$NON-NLS-1$
|
||||
try {
|
||||
IFile manifestFile = AndroidManifestParser.getManifest(project);
|
||||
AndroidManifestParser data = AndroidManifestParser.parseForData(manifestFile);
|
||||
if (data != null) {
|
||||
String javaPackage = data.getPackage();
|
||||
return javaPackage + ".R"; //$NON-NLS-1$
|
||||
}
|
||||
} catch (CoreException e) {
|
||||
// This will typically happen either because the manifest file is not present
|
||||
// and/or the workspace needs to be refreshed.
|
||||
AdtPlugin.logAndPrintError(e,
|
||||
"Android Resources",
|
||||
"Failed to find the package of the AndroidManifest of project %1$s. Reason: %2$s",
|
||||
project.getName(),
|
||||
e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -189,20 +189,15 @@ public class ResourceMonitor implements IResourceChangeListener {
|
||||
// the project is opening or closing.
|
||||
IProject project = (IProject)r;
|
||||
|
||||
// the OPEN flag represent a toggle in the open/close state of the
|
||||
// project, but this is sent before the project actually toggles
|
||||
// its state.
|
||||
// This means that if the project is closing, isOpen() will return true.
|
||||
boolean isClosing = project.isOpen();
|
||||
if (isClosing) {
|
||||
if (project.isOpen()) {
|
||||
// notify the listeners.
|
||||
for (IProjectListener pl : mProjectListeners) {
|
||||
pl.projectClosed(project);
|
||||
pl.projectOpened(project);
|
||||
}
|
||||
} else {
|
||||
// notify the listeners.
|
||||
for (IProjectListener pl : mProjectListeners) {
|
||||
pl.projectOpened(project);
|
||||
pl.projectClosed(project);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -286,6 +281,20 @@ public class ResourceMonitor implements IResourceChangeListener {
|
||||
mFolderListeners.add(bundle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an existing folder listener.
|
||||
* @param listener the listener to remove.
|
||||
*/
|
||||
public synchronized void removeFolderListener(IFolderListener listener) {
|
||||
for (int i = 0 ; i < mFolderListeners.size() ; i++) {
|
||||
FolderListenerBundle bundle = mFolderListeners.get(i);
|
||||
if (bundle.listener == listener) {
|
||||
mFolderListeners.remove(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a project listener.
|
||||
* @param listener The listener to receive the events.
|
||||
@@ -305,10 +314,30 @@ public class ResourceMonitor implements IResourceChangeListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an existing project listener.
|
||||
* @param listener the listener to remove.
|
||||
*/
|
||||
public synchronized void removeProjectListener(IProjectListener listener) {
|
||||
mProjectListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a resource event listener.
|
||||
* @param listener The listener to receive the events.
|
||||
*/
|
||||
public synchronized void addResourceEventListener(IResourceEventListener listener) {
|
||||
mEventListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an existing Resource Event listener.
|
||||
* @param listener the listener to remove.
|
||||
*/
|
||||
public synchronized void removeResourceEventListener(IResourceEventListener listener) {
|
||||
mEventListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the workspace resource change events.
|
||||
*/
|
||||
|
||||
@@ -48,7 +48,7 @@ public class ListValueCellEditor extends ComboBoxCellEditor {
|
||||
UiListAttributeNode uiListAttribute = (UiListAttributeNode)value;
|
||||
|
||||
// set the possible values in the combo
|
||||
String[] items = uiListAttribute.getPossibleValues();
|
||||
String[] items = uiListAttribute.getPossibleValues(null);
|
||||
mItems = new String[items.length];
|
||||
System.arraycopy(items, 0, mItems, 0, items.length);
|
||||
setItems(mItems);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.ide.eclipse.editors.ui.tree;
|
||||
|
||||
import com.android.ide.eclipse.adt.AdtPlugin;
|
||||
import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener;
|
||||
import com.android.ide.eclipse.editors.AndroidEditor;
|
||||
import com.android.ide.eclipse.editors.IconFactory;
|
||||
import com.android.ide.eclipse.editors.descriptors.ElementDescriptor;
|
||||
@@ -26,6 +27,7 @@ import com.android.ide.eclipse.editors.uimodel.IUiUpdateListener;
|
||||
import com.android.ide.eclipse.editors.uimodel.UiDocumentNode;
|
||||
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.jface.action.Action;
|
||||
import org.eclipse.jface.action.IMenuListener;
|
||||
import org.eclipse.jface.action.IMenuManager;
|
||||
@@ -285,13 +287,21 @@ public final class UiTreeBlock extends MasterDetailsBlock implements ICommitXml
|
||||
}
|
||||
};
|
||||
|
||||
final Runnable resourceRefreshListener = new Runnable() {
|
||||
public void run() {
|
||||
/** Listener to update the root node if the target of the file is changed because of a
|
||||
* SDK location change or a project target change */
|
||||
final ITargetChangeListener targetListener = new ITargetChangeListener() {
|
||||
public void onProjectTargetChange(IProject changedProject) {
|
||||
if (changedProject == mEditor.getProject()) {
|
||||
onTargetsLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
public void onTargetsLoaded() {
|
||||
// If a details part has been created, we need to "refresh" it too.
|
||||
if (mDetailsPart != null) {
|
||||
// The details part does not directly expose access to its internal
|
||||
// page book. Instead it is possible to resize the page book to 0 and then
|
||||
// back to its original value, which as the side effect of removing all
|
||||
// back to its original value, which has the side effect of removing all
|
||||
// existing cached pages.
|
||||
int limit = mDetailsPart.getPageLimit();
|
||||
mDetailsPart.setPageLimit(0);
|
||||
@@ -306,7 +316,7 @@ public final class UiTreeBlock extends MasterDetailsBlock implements ICommitXml
|
||||
changeRootAndDescriptors(mUiRootNode, mDescriptorFilters, false /* refresh */);
|
||||
|
||||
// Listen on resource framework changes to refresh the tree
|
||||
AdtPlugin.getDefault().addResourceChangedListener(resourceRefreshListener);
|
||||
AdtPlugin.getDefault().addTargetListener(targetListener);
|
||||
|
||||
// Remove listeners when the tree widget gets disposed.
|
||||
tree.addDisposeListener(new DisposeListener() {
|
||||
@@ -318,7 +328,7 @@ public final class UiTreeBlock extends MasterDetailsBlock implements ICommitXml
|
||||
node.removeUpdateListener(mUiRefreshListener);
|
||||
mUiRootNode.removeUpdateListener(mUiEnableListener);
|
||||
|
||||
AdtPlugin.getDefault().removeResourceChangedListener(resourceRefreshListener);
|
||||
AdtPlugin.getDefault().removeTargetListener(targetListener);
|
||||
if (mClipboard != null) {
|
||||
mClipboard.dispose();
|
||||
mClipboard = null;
|
||||
@@ -580,7 +590,11 @@ public final class UiTreeBlock extends MasterDetailsBlock implements ICommitXml
|
||||
ui_node = ui_node.getUiParent()) {
|
||||
segments.add(0, ui_node);
|
||||
}
|
||||
mTreeViewer.setSelection(new TreeSelection(new TreePath(segments.toArray())));
|
||||
if (segments.size() > 0) {
|
||||
mTreeViewer.setSelection(new TreeSelection(new TreePath(segments.toArray())));
|
||||
} else {
|
||||
mTreeViewer.setSelection(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ public abstract class UiAbstractTextAttributeNode extends UiAttributeNode
|
||||
public void updateValue(Node xml_attribute_node) {
|
||||
mCurrentValue = DEFAULT_VALUE;
|
||||
if (xml_attribute_node != null) {
|
||||
mCurrentValue = xml_attribute_node.getNodeValue().trim();
|
||||
mCurrentValue = xml_attribute_node.getNodeValue();
|
||||
}
|
||||
|
||||
if (isValid() && !getTextWidgetValue().equals(mCurrentValue)) {
|
||||
@@ -101,7 +101,7 @@ public abstract class UiAbstractTextAttributeNode extends UiAttributeNode
|
||||
public void commit() {
|
||||
UiElementNode parent = getUiParent();
|
||||
if (parent != null && isValid() && isDirty()) {
|
||||
String value = getTextWidgetValue().trim();
|
||||
String value = getTextWidgetValue();
|
||||
if (parent.commitAttributeToXml(this, value)) {
|
||||
mCurrentValue = value;
|
||||
setDirty(false);
|
||||
|
||||
@@ -117,9 +117,13 @@ public abstract class UiAttributeNode {
|
||||
* <p/>
|
||||
* Implementations that do not have any known values should return null.
|
||||
*
|
||||
* @return A list of possible completion values or null.
|
||||
* @param prefix An optional prefix string, which is whatever the user has already started
|
||||
* typing. Can be null or an empty string. The implementation can use this to filter choices
|
||||
* and only return strings that match this prefix. A lazy or default implementation can
|
||||
* simply ignore this and return everything.
|
||||
* @return A list of possible completion values, and empty array or null.
|
||||
*/
|
||||
public abstract String[] getPossibleValues();
|
||||
public abstract String[] getPossibleValues(String prefix);
|
||||
|
||||
/**
|
||||
* Called when the XML is being loaded or has changed to
|
||||
|
||||
@@ -124,9 +124,11 @@ public class UiFlagAttributeNode extends UiTextAttributeNode {
|
||||
/**
|
||||
* Get the flag names, either from the initial names set in the attribute
|
||||
* or by querying the framework resource parser.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String[] getPossibleValues() {
|
||||
public String[] getPossibleValues(String prefix) {
|
||||
String attr_name = getDescriptor().getXmlLocalName();
|
||||
String element_name = getUiParent().getDescriptor().getXmlName();
|
||||
|
||||
@@ -242,7 +244,7 @@ public class UiFlagAttributeNode extends UiTextAttributeNode {
|
||||
final TableColumn column = new TableColumn(mTable, SWT.NONE);
|
||||
|
||||
// List all the expected flag names and check those which are currently used
|
||||
String[] names = getPossibleValues();
|
||||
String[] names = getPossibleValues(null);
|
||||
if (names != null) {
|
||||
for (String name : names) {
|
||||
TableItem item = new TableItem(mTable, SWT.NONE);
|
||||
|
||||
@@ -108,7 +108,7 @@ public class UiListAttributeNode extends UiAbstractTextAttributeNode {
|
||||
}
|
||||
|
||||
protected void fillCombo() {
|
||||
String[] values = getPossibleValues();
|
||||
String[] values = getPossibleValues(null);
|
||||
|
||||
if (values == null) {
|
||||
AdtPlugin.log(IStatus.ERROR,
|
||||
@@ -124,9 +124,11 @@ public class UiListAttributeNode extends UiAbstractTextAttributeNode {
|
||||
/**
|
||||
* Get the list values, either from the initial values set in the attribute
|
||||
* or by querying the framework resource parser.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String[] getPossibleValues() {
|
||||
public String[] getPossibleValues(String prefix) {
|
||||
AttributeDescriptor descriptor = getDescriptor();
|
||||
UiElementNode uiParent = getUiParent();
|
||||
|
||||
@@ -134,13 +136,13 @@ public class UiListAttributeNode extends UiAbstractTextAttributeNode {
|
||||
String element_name = uiParent.getDescriptor().getXmlName();
|
||||
|
||||
// FrameworkResourceManager expects a specific prefix for the attribute.
|
||||
String prefix = "";
|
||||
String nsPrefix = "";
|
||||
if (SdkConstants.NS_RESOURCES.equals(descriptor.getNamespaceUri())) {
|
||||
prefix = "android:"; //$NON-NLS-1$
|
||||
nsPrefix = "android:"; //$NON-NLS-1$
|
||||
} else if (XmlnsAttributeDescriptor.XMLNS_URI.equals(descriptor.getNamespaceUri())) {
|
||||
prefix = "xmlns:"; //$NON-NLS-1$
|
||||
nsPrefix = "xmlns:"; //$NON-NLS-1$
|
||||
}
|
||||
attr_name = prefix + attr_name;
|
||||
attr_name = nsPrefix + attr_name;
|
||||
|
||||
String[] values = null;
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.ide.eclipse.editors.uimodel;
|
||||
|
||||
import com.android.ide.eclipse.adt.sdk.AndroidTargetData;
|
||||
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.editors.AndroidEditor;
|
||||
import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor;
|
||||
@@ -44,6 +45,10 @@ import org.eclipse.ui.forms.IManagedForm;
|
||||
import org.eclipse.ui.forms.widgets.FormToolkit;
|
||||
import org.eclipse.ui.forms.widgets.TableWrapData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Represents an XML attribute for a resource that can be modified using a simple text field or
|
||||
* a dialog to choose an existing resource.
|
||||
@@ -153,9 +158,113 @@ public class UiResourceAttributeNode extends UiTextAttributeNode {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the values one could use to auto-complete a "resource" value in an XML
|
||||
* content assist.
|
||||
* <p/>
|
||||
* Typically the user is editing the value of an attribute in a resource XML, e.g.
|
||||
* <pre> "<Button android:test="@string/my_[caret]_string..." </pre>
|
||||
* <p/>
|
||||
*
|
||||
* "prefix" is the value that the user has typed so far (or more exactly whatever is on the
|
||||
* left side of the insertion point). In the example above it would be "@style/my_".
|
||||
* <p/>
|
||||
*
|
||||
* To avoid a huge long list of values, the completion works on two levels:
|
||||
* <ul>
|
||||
* <li> If a resource type as been typed so far (e.g. "@style/"), then limit the values to
|
||||
* the possible completions that match this type.
|
||||
* <li> If no resource type as been typed so far, then return the various types that could be
|
||||
* completed. So if the project has only strings and layouts resources, for example,
|
||||
* the returned list will only include "@string/" and "@layout/".
|
||||
* </ul>
|
||||
*
|
||||
* Finally if anywhere in the string we find the special token "android:", we use the
|
||||
* current framework system resources rather than the project resources.
|
||||
* This works for both "@android:style/foo" and "@style/android:foo" conventions even though
|
||||
* the reconstructed name will always be of the former form.
|
||||
*
|
||||
* Note that "android:" here is a keyword specific to Android resources and should not be
|
||||
* mixed with an XML namespace for an XML attribute name.
|
||||
*/
|
||||
@Override
|
||||
public String[] getPossibleValues() {
|
||||
// TODO: compute a list of existing resources for content assist completion
|
||||
return null;
|
||||
public String[] getPossibleValues(String prefix) {
|
||||
IResourceRepository repository = null;
|
||||
boolean isSystem = false;
|
||||
|
||||
UiElementNode uiNode = getUiParent();
|
||||
AndroidEditor editor = uiNode.getEditor();
|
||||
|
||||
if (prefix == null || prefix.indexOf("android:") < 0) {
|
||||
IProject project = editor.getProject();
|
||||
if (project != null) {
|
||||
// get the resource repository for this project and the system resources.
|
||||
repository = ResourceManager.getInstance().getProjectResources(project);
|
||||
}
|
||||
} else {
|
||||
// If there's a prefix with "android:" in it, use the system resources
|
||||
//
|
||||
// TODO find a way to only list *public* framework resources here.
|
||||
AndroidTargetData data = editor.getTargetData();
|
||||
repository = data.getSystemResources();
|
||||
isSystem = true;
|
||||
}
|
||||
|
||||
// Get list of potential resource types, either specific to this project
|
||||
// or the generic list.
|
||||
ResourceType[] resTypes = (repository != null) ?
|
||||
repository.getAvailableResourceTypes() :
|
||||
ResourceType.values();
|
||||
|
||||
// Get the type name from the prefix, if any. It's any word before the / if there's one
|
||||
String typeName = null;
|
||||
if (prefix != null) {
|
||||
Matcher m = Pattern.compile(".*?([a-z]+)/.*").matcher(prefix);
|
||||
if (m.matches()) {
|
||||
typeName = m.group(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Now collect results
|
||||
ArrayList<String> results = new ArrayList<String>();
|
||||
|
||||
if (typeName == null) {
|
||||
// This prefix does not have a / in it, so the resource string is either empty
|
||||
// or does not have the resource type in it. Simply offer the list of potential
|
||||
// resource types.
|
||||
|
||||
for (ResourceType resType : resTypes) {
|
||||
results.add("@" + resType.getName() + "/");
|
||||
if (resType == ResourceType.ID) {
|
||||
// Also offer the + version to create an id from scratch
|
||||
results.add("@+" + resType.getName() + "/");
|
||||
}
|
||||
}
|
||||
} else if (repository != null) {
|
||||
// We have a style name and a repository. Find all resources that match this
|
||||
// type and recreate suggestions out of them.
|
||||
|
||||
ResourceType resType = ResourceType.getEnum(typeName);
|
||||
if (resType != null) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append('@');
|
||||
if (prefix.indexOf('+') >= 0) {
|
||||
sb.append('+');
|
||||
}
|
||||
|
||||
if (isSystem) {
|
||||
sb.append("android:");
|
||||
}
|
||||
|
||||
sb.append(typeName).append('/');
|
||||
String base = sb.toString();
|
||||
|
||||
for (ResourceItem item : repository.getResources(resType)) {
|
||||
results.add(base + item.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results.toArray(new String[results.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,9 +96,13 @@ public class UiSeparatorAttributeNode extends UiAttributeNode {
|
||||
sep.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
|
||||
}
|
||||
|
||||
/** No completion values for this UI attribute. */
|
||||
/**
|
||||
* No completion values for this UI attribute.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String[] getPossibleValues() {
|
||||
public String[] getPossibleValues(String prefix) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,9 +70,13 @@ public class UiTextAttributeNode extends UiAbstractTextAttributeNode {
|
||||
setTextWidget(text);
|
||||
}
|
||||
|
||||
/** No completion values for this UI attribute. */
|
||||
/**
|
||||
* No completion values for this UI attribute.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String[] getPossibleValues() {
|
||||
public String[] getPossibleValues(String prefix) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.common.project.ProjectChooserHelper;
|
||||
import com.android.ide.eclipse.editors.descriptors.DocumentDescriptor;
|
||||
import com.android.ide.eclipse.editors.descriptors.ElementDescriptor;
|
||||
import com.android.ide.eclipse.editors.descriptors.IDescriptorProvider;
|
||||
import com.android.ide.eclipse.editors.menu.descriptors.MenuDescriptors;
|
||||
import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration;
|
||||
import com.android.ide.eclipse.editors.resources.configurations.ResourceQualifier;
|
||||
@@ -81,6 +82,7 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
private final String mXmlns;
|
||||
private final String mDefaultAttrs;
|
||||
private final String mDefaultRoot;
|
||||
private final int mTargetApiLevel;
|
||||
|
||||
public TypeInfo(String uiName,
|
||||
String tooltip,
|
||||
@@ -88,7 +90,8 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
Object rootSeed,
|
||||
String defaultRoot,
|
||||
String xmlns,
|
||||
String defaultAttrs) {
|
||||
String defaultAttrs,
|
||||
int targetApiLevel) {
|
||||
mUiName = uiName;
|
||||
mResFolderType = resFolderType;
|
||||
mTooltip = tooltip;
|
||||
@@ -96,6 +99,7 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
mDefaultRoot = defaultRoot;
|
||||
mXmlns = xmlns;
|
||||
mDefaultAttrs = defaultAttrs;
|
||||
mTargetApiLevel = targetApiLevel;
|
||||
}
|
||||
|
||||
/** Returns the UI name for the resource type. Unique. Never null. */
|
||||
@@ -176,6 +180,13 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
String getDefaultAttrs() {
|
||||
return mDefaultAttrs;
|
||||
}
|
||||
|
||||
/**
|
||||
* The minimum API level required by the current SDK target to support this feature.
|
||||
*/
|
||||
public int getTargetApiLevel() {
|
||||
return mTargetApiLevel;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,7 +201,8 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
"LinearLayout", // default root
|
||||
SdkConstants.NS_RESOURCES, // xmlns
|
||||
"android:layout_width=\"wrap_content\"\n" + // default attributes
|
||||
"android:layout_height=\"wrap_content\""
|
||||
"android:layout_height=\"wrap_content\"",
|
||||
1 // target API level
|
||||
),
|
||||
new TypeInfo("Values", // UI name
|
||||
"An XML file with simple values: colors, strings, dimensions, etc.", // tooltip
|
||||
@@ -198,7 +210,8 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
ResourcesDescriptors.ROOT_ELEMENT, // root seed
|
||||
null, // default root
|
||||
null, // xmlns
|
||||
null // default attributes
|
||||
null, // default attributes
|
||||
1 // target API level
|
||||
),
|
||||
new TypeInfo("Menu", // UI name
|
||||
"An XML file that describes an menu.", // tooltip
|
||||
@@ -206,7 +219,17 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
MenuDescriptors.MENU_ROOT_ELEMENT, // root seed
|
||||
null, // default root
|
||||
SdkConstants.NS_RESOURCES, // xmlns
|
||||
null // default attributes
|
||||
null, // default attributes
|
||||
1 // target API level
|
||||
),
|
||||
new TypeInfo("AppWidget Provider", // UI name
|
||||
"An XML file that describes a widget provider.", // tooltip
|
||||
ResourceFolderType.XML, // folder type
|
||||
AndroidTargetData.DESCRIPTOR_APPWIDGET_PROVIDER, // root seed
|
||||
null, // default root
|
||||
SdkConstants.NS_RESOURCES, // xmlns
|
||||
null, // default attributes
|
||||
3 // target API level
|
||||
),
|
||||
new TypeInfo("Preference", // UI name
|
||||
"An XML file that describes preferences.", // tooltip
|
||||
@@ -214,15 +237,17 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
AndroidTargetData.DESCRIPTOR_PREFERENCES, // root seed
|
||||
AndroidConstants.CLASS_PREFERENCE_SCREEN, // default root
|
||||
SdkConstants.NS_RESOURCES, // xmlns
|
||||
null // default attributes
|
||||
null, // default attributes
|
||||
1 // target API level
|
||||
),
|
||||
new TypeInfo("Searchable", // UI name
|
||||
"An XML file that describes a searchable [TODO].", // tooltip
|
||||
"An XML file that describes a searchable.", // tooltip
|
||||
ResourceFolderType.XML, // folder type
|
||||
AndroidTargetData.DESCRIPTOR_SEARCHABLE, // root seed
|
||||
null, // default root
|
||||
SdkConstants.NS_RESOURCES, // xmlns
|
||||
null // default attributes
|
||||
null, // default attributes
|
||||
1 // target API level
|
||||
),
|
||||
new TypeInfo("Animation", // UI name
|
||||
"An XML file that describes an animation.", // tooltip
|
||||
@@ -237,10 +262,14 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
},
|
||||
"set", //$NON-NLS-1$ // default root
|
||||
null, // xmlns
|
||||
null // default attributes
|
||||
null, // default attributes
|
||||
1 // target API level
|
||||
),
|
||||
};
|
||||
|
||||
/** Number of columns in the grid layout */
|
||||
final static int NUM_COL = 4;
|
||||
|
||||
/** Absolute destination folder root, e.g. "/res/" */
|
||||
private static String sResFolderAbs = AndroidConstants.WS_RESOURCES + AndroidConstants.WS_SEP;
|
||||
/** Relative destination folder root, e.g. "res/" */
|
||||
@@ -290,7 +319,7 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
|
||||
initializeDialogUnits(parent);
|
||||
|
||||
composite.setLayout(new GridLayout(3, false /*makeColumnsEqualWidth*/));
|
||||
composite.setLayout(new GridLayout(NUM_COL, false /*makeColumnsEqualWidth*/));
|
||||
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
|
||||
|
||||
createProjectGroup(composite);
|
||||
@@ -303,8 +332,9 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
setControl(composite);
|
||||
|
||||
// Update state the first time
|
||||
initializeRootValues();
|
||||
initializeFromSelection(mInitialSelection);
|
||||
initializeRootValues();
|
||||
enableTypesBasedOnApi();
|
||||
validatePage();
|
||||
}
|
||||
|
||||
@@ -418,17 +448,35 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
new Label(parent, SWT.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads the parent with empty cells to match the number of columns of the parent grid.
|
||||
*
|
||||
* @param parent A grid layout with NUM_COL columns
|
||||
* @param col The current number of columns used.
|
||||
* @return 0, the new number of columns used, for convenience.
|
||||
*/
|
||||
private int padWithEmptyCells(Composite parent, int col) {
|
||||
for (; col < NUM_COL; ++col) {
|
||||
emptyCell(parent);
|
||||
}
|
||||
col = 0;
|
||||
return col;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the project & filename fields.
|
||||
* <p/>
|
||||
* The parent must be a GridLayout with 3 colums.
|
||||
* The parent must be a GridLayout with NUM_COL colums.
|
||||
*/
|
||||
private void createProjectGroup(Composite parent) {
|
||||
int col = 0;
|
||||
|
||||
// project name
|
||||
String tooltip = "The Android Project where the new resource file will be created.";
|
||||
Label label = new Label(parent, SWT.NONE);
|
||||
label.setText("Project");
|
||||
label.setToolTipText(tooltip);
|
||||
++col;
|
||||
|
||||
mProjectTextField = new Text(parent, SWT.BORDER);
|
||||
mProjectTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
@@ -438,6 +486,7 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
onProjectFieldUpdated();
|
||||
}
|
||||
});
|
||||
++col;
|
||||
|
||||
mProjectBrowseButton = new Button(parent, SWT.NONE);
|
||||
mProjectBrowseButton.setText("Browse...");
|
||||
@@ -449,12 +498,16 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
}
|
||||
});
|
||||
mProjectChooserHelper = new ProjectChooserHelper(parent.getShell());
|
||||
++col;
|
||||
|
||||
col = padWithEmptyCells(parent, col);
|
||||
|
||||
// file name
|
||||
tooltip = "The name of the resource file to create.";
|
||||
label = new Label(parent, SWT.NONE);
|
||||
label.setText("File");
|
||||
label.setToolTipText(tooltip);
|
||||
++col;
|
||||
|
||||
mFileNameTextField = new Text(parent, SWT.BORDER);
|
||||
mFileNameTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
@@ -464,31 +517,32 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
validatePage();
|
||||
}
|
||||
});
|
||||
++col;
|
||||
|
||||
emptyCell(parent);
|
||||
padWithEmptyCells(parent, col);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the type field, {@link ConfigurationSelector} and the folder field.
|
||||
* <p/>
|
||||
* The parent must be a GridLayout with 3 colums.
|
||||
* The parent must be a GridLayout with NUM_COL colums.
|
||||
*/
|
||||
private void createTypeGroup(Composite parent) {
|
||||
// separator
|
||||
Label label = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
|
||||
label.setLayoutData(newGridData(3, GridData.GRAB_HORIZONTAL));
|
||||
label.setLayoutData(newGridData(NUM_COL, GridData.GRAB_HORIZONTAL));
|
||||
|
||||
// label before type radios
|
||||
label = new Label(parent, SWT.NONE);
|
||||
label.setText("What type of resource would you like to create?");
|
||||
label.setLayoutData(newGridData(3));
|
||||
label.setLayoutData(newGridData(NUM_COL));
|
||||
|
||||
// display the types on three columns of radio buttons.
|
||||
emptyCell(parent);
|
||||
Composite grid = new Composite(parent, SWT.NONE);
|
||||
emptyCell(parent);
|
||||
padWithEmptyCells(parent, 2);
|
||||
|
||||
grid.setLayout(new GridLayout(3, true /*makeColumnsEqualWidth*/));
|
||||
grid.setLayout(new GridLayout(NUM_COL, true /*makeColumnsEqualWidth*/));
|
||||
|
||||
SelectionListener radioListener = new SelectionAdapter() {
|
||||
@Override
|
||||
@@ -501,23 +555,27 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
};
|
||||
|
||||
int n = sTypes.length;
|
||||
int num_lines = n/3;
|
||||
for (int line = 0; line < num_lines; line++) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
TypeInfo type = sTypes[line * 3 + i];
|
||||
Button radio = new Button(grid, SWT.RADIO);
|
||||
type.setWidget(radio);
|
||||
radio.setSelection(false);
|
||||
radio.setText(type.getUiName());
|
||||
radio.setToolTipText(type.getTooltip());
|
||||
radio.addSelectionListener(radioListener);
|
||||
int num_lines = (n + NUM_COL/2) / NUM_COL;
|
||||
for (int line = 0, k = 0; line < num_lines; line++) {
|
||||
for (int i = 0; i < NUM_COL; i++, k++) {
|
||||
if (k < n) {
|
||||
TypeInfo type = sTypes[k];
|
||||
Button radio = new Button(grid, SWT.RADIO);
|
||||
type.setWidget(radio);
|
||||
radio.setSelection(false);
|
||||
radio.setText(type.getUiName());
|
||||
radio.setToolTipText(type.getTooltip());
|
||||
radio.addSelectionListener(radioListener);
|
||||
} else {
|
||||
emptyCell(grid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// label before configuration selector
|
||||
label = new Label(parent, SWT.NONE);
|
||||
label.setText("What type of resource configuration would you like?");
|
||||
label.setLayoutData(newGridData(3));
|
||||
label.setLayoutData(newGridData(NUM_COL));
|
||||
|
||||
// configuration selector
|
||||
emptyCell(parent);
|
||||
@@ -527,6 +585,7 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
gd.heightHint = ConfigurationSelector.HEIGHT_HINT;
|
||||
mConfigSelector.setLayoutData(gd);
|
||||
mConfigSelector.setOnChangeListener(new onConfigSelectorUpdated());
|
||||
emptyCell(parent);
|
||||
|
||||
// folder name
|
||||
String tooltip = "The folder where the file will be generated, relative to the project.";
|
||||
@@ -542,25 +601,23 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
onWsFolderPathUpdated();
|
||||
}
|
||||
});
|
||||
|
||||
emptyCell(parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the root element combo.
|
||||
* <p/>
|
||||
* The parent must be a GridLayout with 3 colums.
|
||||
* The parent must be a GridLayout with NUM_COL colums.
|
||||
*/
|
||||
private void createRootGroup(Composite parent) {
|
||||
// separator
|
||||
Label label = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
|
||||
label.setLayoutData(newGridData(3, GridData.GRAB_HORIZONTAL));
|
||||
label.setLayoutData(newGridData(NUM_COL, GridData.GRAB_HORIZONTAL));
|
||||
|
||||
// label before the root combo
|
||||
String tooltip = "The root element to create in the XML file.";
|
||||
label = new Label(parent, SWT.NONE);
|
||||
label.setText("Select the root element for the XML file:");
|
||||
label.setLayoutData(newGridData(3));
|
||||
label.setLayoutData(newGridData(NUM_COL));
|
||||
label.setToolTipText(tooltip);
|
||||
|
||||
// root combo
|
||||
@@ -572,7 +629,7 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
mRootElementCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
mRootElementCombo.setToolTipText(tooltip);
|
||||
|
||||
emptyCell(parent);
|
||||
padWithEmptyCells(parent, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -690,11 +747,13 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
// get the AndroidTargetData from the project
|
||||
IAndroidTarget target = Sdk.getCurrent().getTarget(mProject);
|
||||
AndroidTargetData data = Sdk.getCurrent().getTargetData(target);
|
||||
ElementDescriptor descriptor = data.getDescriptorProvider(
|
||||
(Integer)rootSeed).getDescriptor();
|
||||
|
||||
HashSet<ElementDescriptor> visited = new HashSet<ElementDescriptor>();
|
||||
initRootElementDescriptor(roots, descriptor, visited);
|
||||
IDescriptorProvider provider = data.getDescriptorProvider((Integer)rootSeed);
|
||||
ElementDescriptor descriptor = provider.getDescriptor();
|
||||
if (descriptor != null) {
|
||||
HashSet<ElementDescriptor> visited = new HashSet<ElementDescriptor>();
|
||||
initRootElementDescriptor(roots, descriptor, visited);
|
||||
}
|
||||
|
||||
// Sort alphabetically.
|
||||
Collections.sort(roots);
|
||||
@@ -743,15 +802,7 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
}
|
||||
|
||||
if (found != mProject) {
|
||||
mProject = found;
|
||||
|
||||
// update the Type with the new descriptors.
|
||||
initializeRootValues();
|
||||
|
||||
// update the combo
|
||||
updateRootCombo(getSelectedType());
|
||||
|
||||
validatePage();
|
||||
changeProject(found);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -761,17 +812,27 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
private void onProjectBrowse() {
|
||||
IJavaProject p = mProjectChooserHelper.chooseJavaProject(mProjectTextField.getText());
|
||||
if (p != null) {
|
||||
mProject = p.getProject();
|
||||
changeProject(p.getProject());
|
||||
mProjectTextField.setText(mProject.getName());
|
||||
|
||||
// update the Type with the new descriptors.
|
||||
initializeRootValues();
|
||||
|
||||
// update the combo
|
||||
updateRootCombo(getSelectedType());
|
||||
|
||||
validatePage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes mProject to the given new project and update the UI accordingly.
|
||||
*/
|
||||
private void changeProject(IProject newProject) {
|
||||
mProject = newProject;
|
||||
|
||||
// enable types based on new API level
|
||||
enableTypesBasedOnApi();
|
||||
|
||||
// update the Type with the new descriptors.
|
||||
initializeRootValues();
|
||||
|
||||
// update the combo
|
||||
updateRootCombo(getSelectedType());
|
||||
|
||||
validatePage();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -985,6 +1046,26 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to enable the type radio buttons depending on the current API level.
|
||||
* <p/>
|
||||
* A type radio button is enabled either if:
|
||||
* - if mProject is null, API level 1 is considered valid
|
||||
* - if mProject is !null, the project->target->API must be >= to the type's API level.
|
||||
*/
|
||||
private void enableTypesBasedOnApi() {
|
||||
|
||||
IAndroidTarget target = mProject != null ? Sdk.getCurrent().getTarget(mProject) : null;
|
||||
int currentApiLevel = 1;
|
||||
if (target != null) {
|
||||
currentApiLevel = target.getApiVersionNumber();
|
||||
}
|
||||
|
||||
for (TypeInfo type : sTypes) {
|
||||
type.getWidget().setEnabled(type.getTargetApiLevel() <= currentApiLevel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the fields, displays errors and warnings.
|
||||
* Enables the finish button if there are no errors.
|
||||
@@ -1017,6 +1098,22 @@ class NewXmlFileCreationPage extends WizardPage {
|
||||
}
|
||||
}
|
||||
|
||||
// -- validate type API level
|
||||
if (error == null) {
|
||||
IAndroidTarget target = Sdk.getCurrent().getTarget(mProject);
|
||||
int currentApiLevel = 1;
|
||||
if (target != null) {
|
||||
currentApiLevel = target.getApiVersionNumber();
|
||||
}
|
||||
|
||||
TypeInfo type = getSelectedType();
|
||||
|
||||
if (type.getTargetApiLevel() > currentApiLevel) {
|
||||
error = "The API level of the selected type (e.g. AppWidget, etc.) is not " +
|
||||
"compatible with the API level of the project.";
|
||||
}
|
||||
}
|
||||
|
||||
// -- validate folder configuration
|
||||
if (error == null) {
|
||||
ConfigurationState state = mConfigSelector.getState();
|
||||
|
||||
@@ -53,6 +53,9 @@ public final class XmlDescriptors implements IDescriptorProvider {
|
||||
/** The root document descriptor for preferences. */
|
||||
private DocumentDescriptor mPrefDescriptor = new DocumentDescriptor("xml_doc", null /* children */); //$NON-NLS-1$
|
||||
|
||||
/** The root document descriptor for widget provider. */
|
||||
private DocumentDescriptor mAppWidgetDescriptor = new DocumentDescriptor("xml_doc", null /* children */); //$NON-NLS-1$
|
||||
|
||||
/** @return the root descriptor for both searchable and preferences. */
|
||||
public DocumentDescriptor getDescriptor() {
|
||||
return mDescriptor;
|
||||
@@ -72,6 +75,11 @@ public final class XmlDescriptors implements IDescriptorProvider {
|
||||
return mPrefDescriptor;
|
||||
}
|
||||
|
||||
/** @return the root descriptor for widget providers. */
|
||||
public DocumentDescriptor getAppWidgetDescriptor() {
|
||||
return mAppWidgetDescriptor;
|
||||
}
|
||||
|
||||
public IDescriptorProvider getSearchableProvider() {
|
||||
return new IDescriptorProvider() {
|
||||
public ElementDescriptor getDescriptor() {
|
||||
@@ -96,6 +104,18 @@ public final class XmlDescriptors implements IDescriptorProvider {
|
||||
};
|
||||
}
|
||||
|
||||
public IDescriptorProvider getAppWidgetProvider() {
|
||||
return new IDescriptorProvider() {
|
||||
public ElementDescriptor getDescriptor() {
|
||||
return mAppWidgetDescriptor;
|
||||
}
|
||||
|
||||
public ElementDescriptor[] getRootElementDescriptors() {
|
||||
return mAppWidgetDescriptor.getChildren();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the document descriptor.
|
||||
* <p/>
|
||||
@@ -103,11 +123,13 @@ public final class XmlDescriptors implements IDescriptorProvider {
|
||||
* all at once.
|
||||
*
|
||||
* @param searchableStyleMap The map style=>attributes for <searchable> from the attrs.xml file
|
||||
* @param appWidgetStyleMap The map style=>attributes for <appwidget-provider> from the attrs.xml file
|
||||
* @param prefs The list of non-group preference descriptions
|
||||
* @param prefGroups The list of preference group descriptions
|
||||
*/
|
||||
public synchronized void updateDescriptors(
|
||||
Map<String, DeclareStyleableInfo> searchableStyleMap,
|
||||
Map<String, DeclareStyleableInfo> appWidgetStyleMap,
|
||||
ViewClassInfo[] prefs, ViewClassInfo[] prefGroups) {
|
||||
|
||||
XmlnsAttributeDescriptor xmlns = new XmlnsAttributeDescriptor(
|
||||
@@ -115,12 +137,17 @@ public final class XmlDescriptors implements IDescriptorProvider {
|
||||
SdkConstants.NS_RESOURCES);
|
||||
|
||||
ElementDescriptor searchable = createSearchable(searchableStyleMap, xmlns);
|
||||
ElementDescriptor appWidget = createAppWidgetProviderInfo(appWidgetStyleMap, xmlns);
|
||||
ElementDescriptor preferences = createPreference(prefs, prefGroups, xmlns);
|
||||
ArrayList<ElementDescriptor> list = new ArrayList<ElementDescriptor>();
|
||||
if (searchable != null) {
|
||||
list.add(searchable);
|
||||
mSearchDescriptor.setChildren(new ElementDescriptor[]{ searchable });
|
||||
}
|
||||
if (appWidget != null) {
|
||||
list.add(appWidget);
|
||||
mAppWidgetDescriptor.setChildren(new ElementDescriptor[]{ appWidget });
|
||||
}
|
||||
if (preferences != null) {
|
||||
list.add(preferences);
|
||||
mPrefDescriptor.setChildren(new ElementDescriptor[]{ preferences });
|
||||
@@ -161,6 +188,28 @@ public final class XmlDescriptors implements IDescriptorProvider {
|
||||
false /* mandatory */ );
|
||||
return searchable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the new ElementDescriptor for <appwidget-provider>
|
||||
*/
|
||||
private ElementDescriptor createAppWidgetProviderInfo(
|
||||
Map<String, DeclareStyleableInfo> appWidgetStyleMap,
|
||||
XmlnsAttributeDescriptor xmlns) {
|
||||
|
||||
if (appWidgetStyleMap == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ElementDescriptor appWidget = createElement(appWidgetStyleMap,
|
||||
"AppWidgetProviderInfo", //$NON-NLS-1$ styleName
|
||||
"appwidget-provider", //$NON-NLS-1$ xmlName
|
||||
"AppWidget Provider", // uiName
|
||||
null, // sdk url
|
||||
xmlns, // extraAttribute
|
||||
null, // childrenElements
|
||||
false /* mandatory */ );
|
||||
return appWidget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new ElementDescriptor constructed from the information given here
|
||||
|
||||
Reference in New Issue
Block a user