Merge commit 'remotes/korg/cupcake' into cupcake_to_master

This commit is contained in:
Jean-Baptiste Queru
2009-03-18 16:57:28 -07:00
302 changed files with 19954 additions and 7180 deletions

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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() {

View File

@@ -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.

View 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;
}

View File

@@ -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);
}
}
}
}
}

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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";
}
}

View File

@@ -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());

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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
}
}

View File

@@ -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;
}
}

View File

@@ -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();

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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()]);
}
}

View File

@@ -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;

View File

@@ -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.

View File

@@ -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;
}
}

View File

@@ -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.
}
}

View File

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

View File

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

View File

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

View File

@@ -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.",

View File

@@ -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.

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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.

View File

@@ -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();
}

View File

@@ -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}.
*/

View File

@@ -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.

View File

@@ -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;

View File

@@ -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();

View File

@@ -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,

View File

@@ -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();

View File

@@ -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$

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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() {

View File

@@ -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;
}
}

View File

@@ -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.
*/

View File

@@ -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);

View File

@@ -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);
}
}
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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);

View File

@@ -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;

View File

@@ -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> "&lt;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()]);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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