diff options
Diffstat (limited to 'eclipse/plugins')
28 files changed, 1134 insertions, 326 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/new_adt_project.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/new_adt_project.png Binary files differnew file mode 100644 index 0000000..0f0e883 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/new_adt_project.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/new_xml.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/new_xml.png Binary files differnew file mode 100644 index 0000000..8273185 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/new_xml.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml index f7c366d..d6c9ac1 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml +++ b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml @@ -18,6 +18,14 @@ <persistent value="true"/> </extension> <extension + id="com.android.ide.eclipse.common.aapt2Problem" + name="Android AAPT Problem" + point="org.eclipse.core.resources.markers"> + <super type="org.eclipse.core.resources.problemmarker"/> + <super type="org.eclipse.core.resources.textmarker"/> + <persistent value="true"/> + </extension> + <extension id="com.android.ide.eclipse.common.aidlProblem" name="Android AIDL Problem" point="org.eclipse.core.resources.markers"> @@ -464,4 +472,31 @@ </enabledWhen> </page> </extension> + <extension + point="org.eclipse.ui.actionSets"> + <actionSet + description="Android Wizards" + id="adt.actionSet1" + label="Android Wizards" + visible="true"> + <action + class="com.android.ide.eclipse.adt.wizards.actions.NewProjectAction" + icon="icons/new_adt_project.png" + id="com.android.ide.eclipse.adt.wizards.actions.NewProjectAction" + label="New Android Project" + style="push" + toolbarPath="android_project" + tooltip="Opens a wizard to help create a new Android project"> + </action> + <action + class="com.android.ide.eclipse.adt.wizards.actions.NewXmlFileAction" + icon="icons/new_xml.png" + id="com.android.ide.eclipse.adt.wizards.actions.NewXmlFileAction" + label="New Android XML File" + style="push" + toolbarPath="android_project" + tooltip="Opens a wizard to help create a new Android XML file"> + </action> + </actionSet> + </extension> </plugin> diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java index 9aa9354..61be3e5 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java @@ -28,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; @@ -173,7 +174,8 @@ public class AdtPlugin extends AbstractUIPlugin { 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 @@ -861,7 +863,6 @@ public class AdtPlugin extends AbstractUIPlugin { /** * Returns the lock object for SDK loading. If you wish to do things while the SDK is loading, * you must synchronize on this object. - * @return */ public final Object getSdkLockObject() { return mPostLoadProjectsToResolve; @@ -986,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) { @@ -1019,24 +1020,27 @@ 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 (getSdkLockObject()) { - mSdkIsLoaded = LoadStatus.FAILED; - mPostLoadProjectsToResolve.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); - 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). @@ -1052,25 +1056,33 @@ public class AdtPlugin extends AbstractUIPlugin { AndroidClasspathContainerInitializer.updateProjects(array); 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(); @@ -1316,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); + } + + /** + * Removes an existing {@link ITargetChangeListener}. + * @see #addTargetListener(ITargetChangeListener) + */ + public void removeTargetListener(ITargetChangeListener listener) { + mTargetChangeListeners.remove(listener); } - public void removeResourceChangedListener(Runnable resourceRefreshListener) { - mResourceRefreshListener.remove(resourceRefreshListener); + /** + * 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() { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java index 96068c2..e71ae47 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java @@ -203,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); @@ -262,19 +265,79 @@ public class ApkBuilder extends BaseBuilder { } } } + + // store the build status in the persistent storage + saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX , mConvertToDex); + saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources); + saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage); + + if (dv != null && 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; + } + + if (outputFolder == null) { + // mark project and exit + markProject(AdtConstants.MARKER_ADT, Messages.Failed_To_Get_Output, + IMarker.SEVERITY_ERROR); + return referencedProjects; + } + + // first thing we do is check that the SDK directory has been setup. + String osSdkFolder = AdtPlugin.getOsSdkFolder(); + + if (osSdkFolder.length() == 0) { + // this has already been checked in the precompiler. Therefore, + // while we do have to cancel the build, we don't have to return + // any error or throw anything. + return referencedProjects; + } + + // 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 && outputFolder != 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; + } + } + } } } - if (mConvertToDex == false && outputFolder != null) { + + // 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; @@ -282,22 +345,17 @@ public class ApkBuilder extends BaseBuilder { } } - // get the extra configs for the project. This will give us a list of custom apk - // to build based on a restricted set of resources. - Map<String, String> configs = Sdk.getCurrent().getProjectApkConfigs(project); - // also check the final file(s)! String finalPackageName = getFileName(project, null /*config*/); - if (mBuildFinalPackage == false && outputFolder != null) { + 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; - } - - if (configs != null) { + } 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) { @@ -306,8 +364,7 @@ public class ApkBuilder extends BaseBuilder { tmp = outputFolder.findMember(filename); if (tmp == null || (tmp instanceof IFile && tmp.exists() == false)) { - String msg = String.format(Messages.s_Missing_Repackaging, - finalPackageName); + String msg = String.format(Messages.s_Missing_Repackaging, filename); AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg); mBuildFinalPackage = true; break; @@ -316,41 +373,6 @@ public class ApkBuilder extends BaseBuilder { } } - // 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. - abortOnBadSetup(project); - - if (dv != null && 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; - } - - if (outputFolder == null) { - // mark project and exit - markProject(AdtConstants.MARKER_ADT, Messages.Failed_To_Get_Output, - IMarker.SEVERITY_ERROR); - return referencedProjects; - } - - // first thing we do is check that the SDK directory has been setup. - String osSdkFolder = AdtPlugin.getOsSdkFolder(); - - if (osSdkFolder.length() == 0) { - // this has already been checked in the precompiler. Therefore, - // while we do have to cancel the build, we don't have to return - // any error or throw anything. - return referencedProjects; - } - // at this point we know if we need to recreate the temporary apk // or the dex file, but we don't know if we simply need to recreate them // because they are missing @@ -396,6 +418,9 @@ public class ApkBuilder extends BaseBuilder { // 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 @@ -552,7 +577,8 @@ public class ApkBuilder extends BaseBuilder { * @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) + * (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, diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java index 6b0810a..e2e9728 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java @@ -149,6 +149,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; @@ -440,8 +449,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; @@ -465,7 +474,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; } @@ -488,8 +497,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; @@ -502,8 +511,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; } @@ -526,8 +535,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; } @@ -542,8 +551,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; } @@ -558,8 +567,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; } @@ -574,7 +583,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; } @@ -659,23 +686,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 @@ -692,16 +721,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); @@ -732,10 +763,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); } } @@ -851,7 +882,7 @@ abstract class BaseBuilder extends IncrementalProjectBuilder { * Aborts the build if the SDK/project setups are broken. This does not * display any errors. * - * @param javaProject The {@link IJavaProject} being compiled. + * @param project The {@link IJavaProject} being compiled. * @throws CoreException */ protected final void abortOnBadSetup(IProject project) throws CoreException { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/DexWrapper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/DexWrapper.java index 26d96d7..65ad4f5 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/DexWrapper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/DexWrapper.java @@ -57,8 +57,11 @@ 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. */ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java index 958cac2..a0e446c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java @@ -234,6 +234,10 @@ public class PreCompilerBuilder extends BaseBuilder { // get the project objects IProject project = getProject(); + + // Top level check to make sure the build can move forward. + abortOnBadSetup(project); + IJavaProject javaProject = JavaCore.create(project); IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project); @@ -284,10 +288,6 @@ public class PreCompilerBuilder extends BaseBuilder { saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES , mCompileResources); // TODO also needs to store the list of aidl to compile/remove - // At this point we have stored what needs to be build, so we can - // do some high level test and abort if needed. - abortOnBadSetup(project); - // if there was some XML errors, we just return w/o doing // anything since we've put some markers in the files anyway. if (dv != null && dv.mXmlError) { @@ -381,7 +381,7 @@ public class PreCompilerBuilder extends BaseBuilder { // mark the manifest file String message = String.format(Messages.Package_s_Doesnt_Exist_Error, mManifestPackage); - BaseProjectHelper.addMarker(manifest, AndroidConstants.MARKER_AAPT, message, + BaseProjectHelper.addMarker(manifest, AndroidConstants.MARKER_AAPT_COMPILE, message, IMarker.SEVERITY_ERROR); AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, message); @@ -409,8 +409,8 @@ public class PreCompilerBuilder extends BaseBuilder { String osManifestPath = manifestLocation.toOSString(); // remove the aapt markers - removeMarkersFromFile(manifest, AndroidConstants.MARKER_AAPT); - removeMarkersFromContainer(resFolder, AndroidConstants.MARKER_AAPT); + removeMarkersFromFile(manifest, AndroidConstants.MARKER_AAPT_COMPILE); + removeMarkersFromContainer(resFolder, AndroidConstants.MARKER_AAPT_COMPILE); AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, Messages.Preparing_Generated_Files); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/preferences/AndroidPreferencePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/preferences/AndroidPreferencePage.java index c27c106..458f78e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/preferences/AndroidPreferencePage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/preferences/AndroidPreferencePage.java @@ -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); + } + } + } } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java index 339dcd0..d686830 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java @@ -266,7 +266,6 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit // We schedule a new job to put the marker after. final String fmessage = markerMessage; Job markerJob = new Job("Android SDK: Resolving error markers") { - @SuppressWarnings("unchecked") @Override protected IStatus run(IProgressMonitor monitor) { try { @@ -296,7 +295,6 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit // 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") { - @SuppressWarnings("unchecked") @Override protected IStatus run(IProgressMonitor monitor) { try { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/properties/AndroidPropertyPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/properties/AndroidPropertyPage.java index 584dd0d..a4c019f 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/properties/AndroidPropertyPage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/properties/AndroidPropertyPage.java @@ -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,28 +55,43 @@ 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(); - - Composite top = new Composite(parent, SWT.NONE); - top.setLayoutData(new GridData(GridData.FILL_BOTH)); - top.setLayout(new GridLayout(1, false)); - Label l = new Label(top, SWT.NONE); - l.setText("Project Target"); - // get the targets from the sdk IAndroidTarget[] targets = null; if (Sdk.getCurrent() != null) { targets = Sdk.getCurrent().getTargets(); } - + // build the UI. + Composite top = new Composite(parent, SWT.NONE); + top.setLayoutData(new GridData(GridData.FILL_BOTH)); + top.setLayout(new GridLayout(1, false)); + + Label l = new Label(top, SWT.NONE); + l.setText("Project Target"); + mSelector = new SdkTargetSelector(top, targets, false /*allowMultipleSelection*/); - 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; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetData.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetData.java index 2309181..a8852e7 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetData.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetData.java @@ -27,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; @@ -43,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_GADGET_PROVIDER = 8; public final static class LayoutBridge { /** Link to the layout bridge */ @@ -51,6 +53,8 @@ public class AndroidTargetData { public LoadStatus status = LoadStatus.LOADING; public ClassLoader classLoader; + + public int apiLevel; } private final IAndroidTarget mTarget; @@ -93,6 +97,7 @@ public class AndroidTargetData { /** * Creates an AndroidTargetData object. + * @param optionalLibraries */ void setExtraData(IResourceRepository systemResourceRepository, AndroidManifestDescriptors manifestDescriptors, @@ -105,6 +110,7 @@ public class AndroidTargetData { String[] broadcastIntentActionValues, String[] serviceIntentActionValues, String[] intentCategoryValues, + IOptionalLibrary[] optionalLibraries, ProjectResources resources, LayoutBridge layoutBridge) { @@ -120,8 +126,9 @@ public class AndroidTargetData { setPermissions(permissionValues); setIntentFilterActionsAndCategories(activityIntentActionValues, broadcastIntentActionValues, serviceIntentActionValues, intentCategoryValues); + setOptionalLibraries(optionalLibraries); } - + public DexWrapper getDexWrapper() { return mDexWrapper; } @@ -151,6 +158,8 @@ public class AndroidTargetData { return ResourcesDescriptors.getInstance(); case DESCRIPTOR_PREFERENCES: return mXmlDescriptors.getPreferencesProvider(); + case DESCRIPTOR_GADGET_PROVIDER: + return mXmlDescriptors.getGadgetProvider(); case DESCRIPTOR_SEARCHABLE: return mXmlDescriptors.getSearchableProvider(); default : @@ -283,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. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetParser.java index aab660d..04baeba 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetParser.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetParser.java @@ -92,7 +92,7 @@ public final class AndroidTargetParser { try { SubMonitor progress = SubMonitor.convert(monitor, String.format("Parsing SDK %1$s", mAndroidTarget.getName()), - 200); + 14); AndroidTargetData targetData = new AndroidTargetData(mAndroidTarget); @@ -107,15 +107,14 @@ public final class AndroidTargetParser { // we have loaded dx. targetData.setDexWrapper(dexWrapper); + progress.worked(1); // parse the rest of the data. - progress.setWorkRemaining(120); 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; @@ -124,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; @@ -133,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; @@ -147,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; @@ -158,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>(); @@ -171,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; @@ -185,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; @@ -202,6 +203,11 @@ public final class AndroidTargetParser { attrsManifestXmlParser); Map<String, Map<String, Integer>> enumValueMap = attrsXmlParser.getEnumFlagValues(); + Map<String, DeclareStyleableInfo> xmlGadgetMap = null; + if (mAndroidTarget.getApiVersionNumber() >= 3) { + xmlGadgetMap = collectGadgetDefinitions(attrsXmlParser); + } + if (progress.isCanceled()) { return Status.CANCEL_STATUS; } @@ -210,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; @@ -218,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; @@ -226,25 +232,28 @@ 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, + xmlGadgetMap, + 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. targetData.setExtraData(frameworkRepository, @@ -258,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); @@ -268,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(); - } } } @@ -605,6 +611,31 @@ public final class AndroidTargetParser { } /** + * Collects all gadgetProviderInfo definition information from the attrs.xml and returns it. + * + * @param attrsXmlParser The parser of the attrs.xml file + */ + private Map<String, DeclareStyleableInfo> collectGadgetDefinitions( + AttrsXmlParser attrsXmlParser) { + Map<String, DeclareStyleableInfo> map = attrsXmlParser.getDeclareStyleableList(); + Map<String, DeclareStyleableInfo> map2 = new HashMap<String, DeclareStyleableInfo>(); + for (String key : new String[] { "GadgetProviderInfo" }) { //$NON-NLS-1$ + if (map.containsKey(key)) { + map2.put(key, map.get(key)); + } else { + AdtPlugin.log(IStatus.WARNING, + "Gadget declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$ + key, attrsXmlParser.getOsAttrsXmlPath()); + AdtPlugin.printErrorToConsole("Android Framework Parser", + String.format("Gadget declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$ + key, attrsXmlParser.getOsAttrsXmlPath())); + } + } + + return Collections.unmodifiableMap(map2); + } + + /** * Collects all manifest definition information from the attrs_manifest.xml and returns it. */ private Map<String, DeclareStyleableInfo> collectManifestDefinitions( @@ -650,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; } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/Sdk.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/Sdk.java index c7773cc..ba0b568 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/Sdk.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/Sdk.java @@ -18,6 +18,7 @@ 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; @@ -33,6 +34,7 @@ import com.android.sdklib.project.ProjectProperties.PropertyType; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; @@ -69,6 +71,22 @@ public class Sdk implements IProjectListener { private final String mDocBaseUrl; /** + * Classes implementing this interface will receive notification when targets are changed. + */ + public interface ITargetChangeListener { + /** + * Sent when project has its target changed. + */ + void onProjectTargetChange(IProject changedProject); + + /** + * Called when the targets are loaded (either the SDK finished loading when Eclipse starts, + * or the SDK is changed). + */ + void onTargetsLoaded(); + } + + /** * Loads an SDK and returns an {@link Sdk} object if success. * @param sdkLocation the OS path to the SDK. */ @@ -163,24 +181,87 @@ public class Sdk implements IProjectListener { } /** - * 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) { + public void setProject(IProject project, IAndroidTarget target, + Map<String, String> apkConfigMap) { synchronized (mProjectTargetMap) { - // look for the current target of the project - IAndroidTarget previousTarget = mProjectTargetMap.get(project); + boolean resolveProject = false; + boolean compileProject = false; + boolean cleanProject = false; + + ProjectProperties properties = ProjectProperties.load( + project.getLocation().toOSString(), PropertyType.DEFAULT); + if (properties == null) { + // doesn't exist yet? we create it. + properties = ProjectProperties.create(project.getLocation().toOSString(), + PropertyType.DEFAULT); + } + + if (target != null) { + // look for the current target of the project + IAndroidTarget previousTarget = mProjectTargetMap.get(project); + + if (target != previousTarget) { + // save the target hash string in the project persistent property + properties.setAndroidTarget(target); + + // put it in a local map for easy access. + mProjectTargetMap.put(project, target); + + resolveProject = true; + } + } - if (target != previousTarget) { - // save the target hash string in the project persistent property - setProjectTargetHashString(project, target.hashString()); - + 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. - mProjectTargetMap.put(project, target); + mProjectApkConfigMap.put(project, apkConfigMap); + + compileProject = true; + } - // recompile the project if needed. + // 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); } } } @@ -218,7 +299,12 @@ public class Sdk implements IProjectListener { */ 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'", @@ -229,7 +315,7 @@ public class Sdk implements IProjectListener { if (sdkStorage != null) { Map<String, String> configMap = ApkConfigurationHelper.getConfigs(properties); - if (configMap.size() > 0) { + if (configMap != null) { sdkStorage.mProjectApkConfigMap.put(project, configMap); } } @@ -296,40 +382,6 @@ public class Sdk implements IProjectListener { return mProjectApkConfigMap.get(project); } - public void setProjectApkConfigs(IProject project, Map<String, String> configMap) - throws CoreException { - // first set the new map - mProjectApkConfigMap.put(project, configMap); - - // Now we write this in default.properties. - // Because we don't want to erase other properties from default.properties, we first load - // them - ProjectProperties properties = ProjectProperties.load(project.getLocation().toOSString(), - PropertyType.DEFAULT); - if (properties == null) { - // doesn't exist yet? we create it. - properties = ProjectProperties.create(project.getLocation().toOSString(), - PropertyType.DEFAULT); - } - - // sets the configs in the property file. - boolean hasRemovedConfig = ApkConfigurationHelper.setConfigs(properties, configMap); - - // and rewrite the file. - try { - properties.save(); - } catch (IOException e) { - AdtPlugin.log(e, "Failed to save default.properties for project '%s'", - project.getName()); - } - - // we're done, force a rebuild. If there was removed config, we clean instead of build - // (to remove the obsolete ap_ and apk file from removed configs). - project.build(hasRemovedConfig ? - IncrementalProjectBuilder.CLEAN_BUILD : IncrementalProjectBuilder.FULL_BUILD, - null); - } - /** * Returns the {@link AvdManager}. If the AvdManager failed to parse the AVD folder, this could * be <code>null</code>. @@ -402,8 +454,24 @@ public class Sdk implements IProjectListener { } public void projectClosed(IProject project) { - mProjectTargetMap.remove(project); - mProjectApkConfigMap.remove(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) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewProjectAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewProjectAction.java new file mode 100644 index 0000000..e0d0d5e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewProjectAction.java @@ -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(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileAction.java new file mode 100644 index 0000000..8c4a115 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/NewXmlFileAction.java @@ -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(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/OpenWizardAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/OpenWizardAction.java new file mode 100644 index 0000000..4fc9dee --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/actions/OpenWizardAction.java @@ -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 + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java index 607159a..cb79796 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java @@ -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,20 @@ 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 IJavaProject javaProject = JavaCore.create(project); - setupSourceFolder(javaProject, sourceFolder[0], monitor); + for (String sourceFolder : sourceFolders) { + setupSourceFolder(javaProject, sourceFolder, monitor); + } if (((Boolean) parameters.get(PARAM_IS_NEW_PROJECT)).booleanValue()) { // Create files in the project if they don't already exist @@ -359,7 +366,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 +378,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. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java index b1c57a6..e201132 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java @@ -148,8 +148,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$ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidEditor.java index dca7db0..c7541e9 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidEditor.java @@ -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(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/DescriptorsUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/DescriptorsUtils.java index cc923bf..f1d62a1 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/DescriptorsUtils.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/DescriptorsUtils.java @@ -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. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java index ca7cac5..eb7dee6 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java @@ -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; @@ -198,13 +199,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 +237,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 +262,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 +570,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); @@ -1026,25 +1034,36 @@ 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 */ 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; } } @@ -1648,7 +1667,9 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette /** * Recomputes the layout with the help of layoutlib. */ + @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 @@ -1763,20 +1784,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) { @@ -1921,8 +1969,7 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette * Responds to a page change that made the Graphical editor page the activated page. */ void activated() { - mActive = true; - if (mNeedsRecompute) { + if (mNeedsRecompute || mNeedsXmlReload) { recomputeLayout(); } } @@ -1931,7 +1978,7 @@ public class GraphicalLayoutEditor extends GraphicalEditorWithPalette * Responds to a page change that made the Graphical editor page the deactivated page */ void deactivated() { - mActive = false; + // nothing to be done here for now. } /** diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutEditor.java index 880ee2b..dabe797 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutEditor.java @@ -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. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/AndroidManifestDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/AndroidManifestDescriptors.java index 61b73a2..77c08b5 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/AndroidManifestDescriptors.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/AndroidManifestDescriptors.java @@ -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$ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ClassAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ClassAttributeDescriptor.java index abaf438..629b37c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ClassAttributeDescriptor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ClassAttributeDescriptor.java @@ -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. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiTreeBlock.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiTreeBlock.java index e3255d9..fc384e8 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiTreeBlock.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiTreeBlock.java @@ -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); + } } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java index 4d17176..5781938 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java @@ -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("Gadget Provider", // UI name + "An XML file that describes a gadget provider.", // tooltip + ResourceFolderType.XML, // folder type + AndroidTargetData.DESCRIPTOR_GADGET_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(); } @@ -419,16 +449,34 @@ class NewXmlFileCreationPage extends WizardPage { } /** + * 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(); } /** @@ -986,6 +1047,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. gadget, etc.) is not " + + "compatible with the API level of the project."; + } + } + // -- validate folder configuration if (error == null) { ConfigurationState state = mConfigSelector.getState(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/descriptors/XmlDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/descriptors/XmlDescriptors.java index fa1370f..7929b5a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/descriptors/XmlDescriptors.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/descriptors/XmlDescriptors.java @@ -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 gadget provider. */ + private DocumentDescriptor mGadgetDescriptor = 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 gadget providers. */ + public DocumentDescriptor getGadgetDescriptor() { + return mGadgetDescriptor; + } + public IDescriptorProvider getSearchableProvider() { return new IDescriptorProvider() { public ElementDescriptor getDescriptor() { @@ -96,6 +104,18 @@ public final class XmlDescriptors implements IDescriptorProvider { }; } + public IDescriptorProvider getGadgetProvider() { + return new IDescriptorProvider() { + public ElementDescriptor getDescriptor() { + return mGadgetDescriptor; + } + + public ElementDescriptor[] getRootElementDescriptors() { + return mGadgetDescriptor.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 gadgetStyleMap The map style=>attributes for <gadget-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> gadgetStyleMap, 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 gadget = createGadgetProviderInfo(gadgetStyleMap, 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 (gadget != null) { + list.add(gadget); + mGadgetDescriptor.setChildren(new ElementDescriptor[]{ gadget }); + } 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 <gadget-provider> + */ + private ElementDescriptor createGadgetProviderInfo( + Map<String, DeclareStyleableInfo> gadgetStyleMap, + XmlnsAttributeDescriptor xmlns) { + + if (gadgetStyleMap == null) { + return null; + } + + ElementDescriptor gadget = createElement(gadgetStyleMap, + "GadgetProviderInfo", //$NON-NLS-1$ styleName + "gadget-provider", //$NON-NLS-1$ xmlName + "Gadget Provider", // uiName + null, // sdk url + xmlns, // extraAttribute + null, // childrenElements + false /* mandatory */ ); + return gadget; + } /** * Returns a new ElementDescriptor constructed from the information given here |