diff options
29 files changed, 726 insertions, 96 deletions
diff --git a/anttasks/src/com/android/ant/AaptExecLoopTask.java b/anttasks/src/com/android/ant/AaptExecLoopTask.java index 6b438bb..7b7d82d 100644 --- a/anttasks/src/com/android/ant/AaptExecLoopTask.java +++ b/anttasks/src/com/android/ant/AaptExecLoopTask.java @@ -348,7 +348,7 @@ public final class AaptExecLoopTask extends BaseTask { // Now we figure out what we need to do if (generateRClass) { // Check to see if our dependencies have changed. If not, then skip - if (initDependencies(mRFolder + File.separator + "R.d", watchPaths) + if (initDependencies(mRFolder + File.separator + "R.java.d", watchPaths) && dependenciesHaveChanged() == false) { System.out.println("No changed resources. R.java and Manifest.java untouched."); return; @@ -357,13 +357,12 @@ public final class AaptExecLoopTask extends BaseTask { // Find our dependency file. It should have the same name as our target .ap_ but // with a .d extension String dependencyFilePath = mApkFolder + File.separator + mApkName; - dependencyFilePath = - dependencyFilePath.substring(0, dependencyFilePath.lastIndexOf(".")) + ".d"; + dependencyFilePath += ".d"; // Check to see if our dependencies have changed if (initDependencies(dependencyFilePath , watchPaths) && dependenciesHaveChanged() == false) { - System.out.println("No changed resources or assets. " + dependencyFilePath + System.out.println("No changed resources or assets. " + mApkName + " remains untouched"); return; } diff --git a/anttasks/src/com/android/ant/BaseTask.java b/anttasks/src/com/android/ant/BaseTask.java index 00b7fcb..0ff7bf1 100644 --- a/anttasks/src/com/android/ant/BaseTask.java +++ b/anttasks/src/com/android/ant/BaseTask.java @@ -21,6 +21,8 @@ import org.apache.tools.ant.Task; import java.io.File; import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; /** * A base class for the ant task that contains logic for handling dependency files @@ -41,6 +43,22 @@ public abstract class BaseTask extends Task { protected abstract String getExecTaskName(); + private Set<String> mRestrictTouchedExtensionsTo; + + /** + * Sets the value of the "restricttouchedextensionsto" attribute. + * @param touchedExtensions the extensions to check to see if they have been modified. + * values should be separated by a colon (:). If left blank or not set, all extensions + * will be checked. + */ + public void setRestrictTouchedExtensionsTo(String restrictTouchedExtensionsTo) { + mRestrictTouchedExtensionsTo = new HashSet<String>(); + String[] extensions = restrictTouchedExtensionsTo.split(":"); + for (String s : extensions) { + mRestrictTouchedExtensionsTo.add(s); + } + } + @Override public void execute() throws BuildException { @@ -84,6 +102,6 @@ public abstract class BaseTask extends Task { } assert mDependencies != null : "Dependencies have not been initialized"; - return mDependencies.dependenciesHaveChanged(); + return mDependencies.dependenciesHaveChanged(mRestrictTouchedExtensionsTo); } } diff --git a/anttasks/src/com/android/ant/DependencyGraph.java b/anttasks/src/com/android/ant/DependencyGraph.java index 5939a92..4c85860 100644 --- a/anttasks/src/com/android/ant/DependencyGraph.java +++ b/anttasks/src/com/android/ant/DependencyGraph.java @@ -36,7 +36,7 @@ public class DependencyGraph { // Files that we know about from the dependency file private Set<File> mTargets = Collections.emptySet(); private Set<File> mPrereqs = mTargets; - private ArrayList<File> mWatchPaths; + private final ArrayList<File> mWatchPaths; public DependencyGraph(String dependencyFilePath, ArrayList<File> watchPaths) { mWatchPaths = watchPaths; @@ -45,15 +45,18 @@ public class DependencyGraph { /** * Check all the dependencies to see if anything has changed. + * @param extensionsToCheck a set of extensions. Only files with an extension in this set will + * be considered for a modification check. All deleted/created files will still be + * checked. If this is null, all files will be checked for modification date * @return true if new prerequisites have appeared, target files are missing or if * prerequisite files have been modified since the last target generation. */ - public boolean dependenciesHaveChanged() { + public boolean dependenciesHaveChanged(Set<String> extensionsToCheck) { boolean noFile = (mTargets.size() == 0); boolean missingPrereq = missingPrereqFile(); boolean newPrereq = newPrereqFile(); boolean missingTarget = missingTargetFile(); - boolean modPrereq = modifiedPrereq(); + boolean modPrereq = modifiedPrereq(extensionsToCheck); if (noFile) { System.out.println("No Dependency File Found"); @@ -206,7 +209,7 @@ public class DependencyGraph { * @return true if the latest prerequisite modification is after the oldest * target modification. */ - private boolean modifiedPrereq() { + private boolean modifiedPrereq(Set<String> extensionsToCheck) { // Find the oldest target long oldestTarget = Long.MAX_VALUE; for (File target : mTargets) { @@ -218,8 +221,12 @@ public class DependencyGraph { // Find the newest prerequisite long newestPrereq = 0; for (File prereq : mPrereqs) { - if (prereq.lastModified() > newestPrereq) { - newestPrereq = prereq.lastModified(); + // If we have a list of extensions that we need to restrict ourselves to, only + // consider this file if it has that extension. + if (extensionsToCheck == null || extensionsToCheck.contains(getExtension(prereq))) { + if (prereq.lastModified() > newestPrereq) { + newestPrereq = prereq.lastModified(); + } } } @@ -251,4 +258,19 @@ public class DependencyGraph { } return null; } + + /** + * Gets the extension (if present) on a file by looking at the filename + * @param file the file to get the extension of + * @return the extension if present, or the empty string if the filename doesn't have + * and extension. + */ + private static String getExtension(File file) { + String filename = file.getName(); + if (filename.lastIndexOf('.') == -1) { + return ""; + } + // Don't include the leading '.' in the extension + return filename.substring(filename.lastIndexOf('.') + 1); + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/build_messages.properties b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/build_messages.properties index aabe2a4..28ceebd 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/build_messages.properties +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/build_messages.properties @@ -37,7 +37,7 @@ AIDL_Java_Conflict=%1$s is in the way of %2$s, remove it or rename of one the fi AIDL_Exec_Error=Error executing aidl. Please check aidl is present at %1$s s_Removed_Recreating_s=%1$s was removed\! Recreating %1$s\! s_Modified_Manually_Recreating_s=%1$s was modified manually\! Reverting to generated version\! -s_Modified_Recreating_s='%1$s' was modified, %2$s needs to be updated. +s_Modified_Recreating_s='%1$s' was modified. Added_s_s_Needs_Updating=New resource file: '%1$s', %2$s needs to be updated. s_Removed_s_Needs_Updating='%1$s' was removed, %2$s needs to be updated. Requires_Compiler_Compliance_s=Android requires compiler compliance level 5.0 or 6.0. Found '%1$s' instead. Please use Android Tools > Fix Project Properties. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java index 2a988d1..3ece0e5 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java @@ -255,10 +255,15 @@ public class PreCompilerBuilder extends BaseBuilder { dv = new PreCompilerDeltaVisitor(this, sourceFolderPathList, mProcessors); delta.accept(dv); // Notify the ResourceManager: - ResourceManager.getInstance().processDelta(delta); + ResourceManager resManager = ResourceManager.getInstance(); + resManager.processDelta(delta); - // record the state + // Check whether this project or its dependencies (libraries) have + // resources that need compilation + mMustCompileResources |= resManager.projectNeedsIdGeneration(project); + // Check to see if Manifest.xml, Manifest.java, or R.java have changed: mMustCompileResources |= dv.getCompileResources(); + for (SourceProcessor processor : mProcessors) { processor.doneVisiting(project); } @@ -266,24 +271,6 @@ public class PreCompilerBuilder extends BaseBuilder { // get the java package from the visitor javaPackage = dv.getManifestPackage(); minSdkVersion = dv.getMinSdkVersion(); - - // if the main resources didn't change, then we check for the library - // ones (will trigger resource recompilation too) - if (mMustCompileResources == false && libProjects.size() > 0) { - for (IProject libProject : libProjects) { - delta = getDelta(libProject); - if (delta != null) { - LibraryDeltaVisitor visitor = new LibraryDeltaVisitor(); - delta.accept(visitor); - - mMustCompileResources = visitor.getResChange(); - - if (mMustCompileResources) { - break; - } - } - } - } } } @@ -496,6 +483,7 @@ public class PreCompilerBuilder extends BaseBuilder { handleResources(project, javaPackage, projectTarget, manifestFile, libProjects, projectState.isLibrary()); saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES , false); + // The project resources will find out that they're in sync when their IDs are set } if (processorStatus == SourceProcessor.COMPILE_STATUS_NONE && diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerDeltaVisitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerDeltaVisitor.java index cd99fbe..5f08856 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerDeltaVisitor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerDeltaVisitor.java @@ -16,11 +16,11 @@ package com.android.ide.eclipse.adt.internal.build.builders; -import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.build.Messages; import com.android.ide.eclipse.adt.internal.build.SourceChangeHandler; import com.android.ide.eclipse.adt.internal.build.SourceProcessor; -import com.android.ide.eclipse.adt.internal.build.Messages; import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder.BaseDeltaVisitor; import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity; import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; @@ -60,9 +60,8 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements IResourceDelta // Result fields. /** * Compile flag. This is set to true if one of the changed/added/removed - * file is a resource file. Upon visiting all the delta resources, if - * this flag is true, then we know we'll have to compile the resources - * into R.java + * files is Manifest.xml, Manifest.java, or R.java. All other file changes + * will be taken care of by ResourceManager. */ private boolean mCompileResources = false; @@ -88,12 +87,12 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements IResourceDelta private IFolder mSourceFolder = null; /** List of source folders. */ - private List<IPath> mSourceFolders; + private final List<IPath> mSourceFolders; private boolean mIsGenSourceFolder = false; private final List<SourceChangeHandler> mSourceChangeHandlers = new ArrayList<SourceChangeHandler>(); - private IWorkspaceRoot mRoot; + private final IWorkspaceRoot mRoot; @@ -109,6 +108,10 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements IResourceDelta } } + /** + * Get whether Manifest.java, Manifest.xml, or R.java have changed + * @return true if any of Manifest.xml, Manifest.java, or R.java have been modified + */ public boolean getCompileResources() { return mCompileResources; } @@ -323,8 +326,7 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements IResourceDelta switch (kind) { case IResourceDelta.CHANGED: // display verbose message - message = String.format(Messages.s_Modified_Recreating_s, p, - AdtConstants.FN_RESOURCE_CLASS); + message = String.format(Messages.s_Modified_Recreating_s, p); break; case IResourceDelta.ADDED: // display verbose message @@ -345,26 +347,13 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements IResourceDelta for (SourceChangeHandler handler : mSourceChangeHandlers) { handler.handleResourceFile((IFile)resource, kind); } - - if (AdtConstants.EXT_XML.equalsIgnoreCase(ext)) { - if (kind != IResourceDelta.REMOVED) { - // check xml Validity - mBuilder.checkXML(resource, this); - } - - // if we are going through this resource, it was modified - // somehow. - // we don't care if it was an added/removed/changed event - mCompileResources = true; - return false; - } else { - // this is a non xml resource. - if (kind == IResourceDelta.ADDED - || kind == IResourceDelta.REMOVED) { - mCompileResources = true; - return false; - } + // If it's an XML resource, check the syntax + if (AdtConstants.EXT_XML.equalsIgnoreCase(ext) && kind != IResourceDelta.REMOVED) { + // check xml Validity + mBuilder.checkXML(resource, this); } + // Whether or not to generate R.java for a changed resource is taken care of by the + // Resource Manager. } else if (resource instanceof IFolder) { // in this case we may be inside a folder that contains a source // folder, go through the list of known source folders diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlAutoEditStrategy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlAutoEditStrategy.java index 2ec328d..e8d0aee 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlAutoEditStrategy.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlAutoEditStrategy.java @@ -15,6 +15,7 @@ */ package com.android.ide.eclipse.adt.internal.editors; +import static org.eclipse.wst.xml.core.internal.regions.DOMRegionContext.XML_CONTENT; import static org.eclipse.wst.xml.core.internal.regions.DOMRegionContext.XML_EMPTY_TAG_CLOSE; import static org.eclipse.wst.xml.core.internal.regions.DOMRegionContext.XML_END_TAG_OPEN; import static org.eclipse.wst.xml.core.internal.regions.DOMRegionContext.XML_TAG_CLOSE; @@ -172,6 +173,93 @@ public class AndroidXmlAutoEditStrategy implements IAutoEditStrategy { sb.append(lineIndent); } c.text = sb.toString(); + } else if (region != null && region.getType().equals(XML_CONTENT)) { + // Indenting in text content. If you're in the middle of editing + // text, just copy the current line indentation. + // However, if you're editing in leading whitespace (e.g. you press + // newline on a blank line following say an element) then figure + // out the indentation as if the newline had been pressed at the + // end of the element, and insert that amount of indentation. + // In this case we need to also make sure to subtract any existing + // whitespace on the current line such that if we have + // + // <foo> + // ^ <bar/> + // </foo> + // + // you end up with + // + // <foo> + // + // ^<bar/> + // </foo> + // + String text = region.getText(); + int regionStart = region.getStartOffset(); + int delta = offset - regionStart; + boolean inWhitespacePrefix = true; + for (int i = 0, n = Math.min(delta, text.length()); i < n; i++) { + char ch = text.charAt(i); + if (!Character.isWhitespace(ch)) { + inWhitespacePrefix = false; + break; + } + } + if (inWhitespacePrefix) { + IStructuredDocumentRegion previous = region.getPrevious(); + if (previous != null && previous.getType() == XML_TAG_NAME) { + ITextRegionList subRegions = previous.getRegions(); + ITextRegion last = subRegions.get(subRegions.size() - 1); + if (last.getType() == XML_TAG_CLOSE || + last.getType() == XML_EMPTY_TAG_CLOSE) { + int begin = AndroidXmlCharacterMatcher.findTagBackwards(doc, + previous.getStartOffset() + last.getStart(), 0); + int prevLineStart = findLineStart(doc, begin); + int prevTextStart = findTextStart(doc, prevLineStart, begin); + + String lineIndent = ""; //$NON-NLS-1$ + if (prevTextStart > prevLineStart) { + lineIndent = doc.get(prevLineStart, + prevTextStart - prevLineStart); + } + StringBuilder sb = new StringBuilder(c.text); + sb.append(lineIndent); + String oneIndentUnit = + XmlFormatPreferences.create().getOneIndentUnit(); + + // See if there is whitespace on the insert line that + // we should also remove + for (int i = delta, n = text.length(); i < n; i++) { + char ch = text.charAt(i); + if (ch == ' ') { + c.length++; + } else { + break; + } + } + boolean onClosingTagLine = false; + if (text.indexOf('\n', delta) == -1) { + IStructuredDocumentRegion next = region.getNext(); + if (next != null && next.getType() == XML_TAG_NAME) { + String nextType = next.getRegions().get(0).getType(); + if (nextType == XML_END_TAG_OPEN) { + onClosingTagLine = true;; + } + } + } + + boolean addIndent = (last.getType() == XML_TAG_CLOSE) + && !onClosingTagLine; + if (addIndent) { + sb.append(oneIndentUnit); + } + c.text = sb.toString(); + + return; + } + } + } + copyPreviousLineIndentation(doc, c); } else { copyPreviousLineIndentation(doc, c); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlCharacterMatcher.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlCharacterMatcher.java index abf446e..8a12fe0 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlCharacterMatcher.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlCharacterMatcher.java @@ -36,6 +36,9 @@ import org.eclipse.wst.xml.ui.internal.text.XMLDocumentRegionEdgeMatcher; */ @SuppressWarnings("restriction") public class AndroidXmlCharacterMatcher extends XMLDocumentRegionEdgeMatcher { + /** + * Constructs a new character matcher for Android XML files + */ public AndroidXmlCharacterMatcher() { } @@ -184,6 +187,15 @@ public class AndroidXmlCharacterMatcher extends XMLDocumentRegionEdgeMatcher { return -1; } + /** + * Finds the corresponding closing tag by searching forwards until the tag balance + * reaches a given target. + * + * @param doc the document + * @param start the starting offset (where the search begins searching forwards from) + * @param targetTagBalance the balance to end the search at + * @return the offset of the beginning of the closing tag + */ public static int findTagForwards(IStructuredDocument doc, int start, int targetTagBalance) { int tagBalance = 0; IStructuredDocumentRegion region = doc.getRegionAtCharacterOffset(start); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java index 74960cf..6d878a7 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java @@ -468,6 +468,12 @@ public class GestureManager { } if (mCurrentGesture == null) { updateCursor(mousePos); + } else if (mCurrentGesture instanceof DropGesture) { + // Mouse Up shouldn't be delivered in the middle of a drag & drop - + // but this can happen on some versions of Linux + // (see http://code.google.com/p/android/issues/detail?id=19057 ) + // and if we process the mouseUp it will abort the remainder of + // the drag & drop operation, so ignore this event! } else { finishGesture(mousePos, false); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java index ef7deaa..eb24870 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java @@ -1371,7 +1371,8 @@ public class UiElementNode implements IPropertySource { } /** - * Updates the {@link UiAttributeNode} list for this {@link UiElementNode}. + * Updates the {@link UiAttributeNode} list for this {@link UiElementNode} + * using the values from the XML element. * <p/> * For a given {@link UiElementNode}, the attribute list always exists in * full and is totally independent of whether the XML model actually @@ -1456,6 +1457,12 @@ public class UiElementNode implements IPropertySource { } } + /** + * Create a new temporary text attribute descriptor for the unknown attribute + * and returns a new {@link UiAttributeNode} associated to this descriptor. + * <p/> + * The attribute is not marked as dirty, doing so is up to the caller. + */ private UiAttributeNode addUnknownAttribute(String xmlFullName, String xmlAttrLocalName, String xmlNsUri) { // Create a new unknown attribute of format string @@ -1467,7 +1474,6 @@ public class UiElementNode implements IPropertySource { new AttributeInfo(xmlAttrLocalName, new Format[] { Format.STRING } ) ); UiAttributeNode uiAttr = desc.createUiNode(this); - uiAttr.setDirty(true); mUnknownUiAttributes.add(uiAttr); mCachedAllUiAttributes = null; return uiAttr; @@ -1815,6 +1821,9 @@ public class UiElementNode implements IPropertySource { // We've created the attribute, but not actually set the value on it, so let's do it. // Try with the updated internal attributes. + // Implementation detail: we could just do a setCurrentValue + setDirty on the + // uiAttr returned by addUnknownAttribute(); however going through setInternalAttrValue + // means we won't duplicate the logic, at the expense of doing one more lookup. uiAttr = setInternalAttrValue( getAllUiAttributes(), attrXmlName, attrNsUri, value, override); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java index c917d1c..6ef1710 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java @@ -32,7 +32,6 @@ import org.eclipse.core.resources.IMarkerDelta; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceDelta; -import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; @@ -79,9 +78,7 @@ public final class CompiledResourcesMonitor implements IFileListener, IProjectLi * @see IFileListener#fileChanged */ public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) { - // Don't execute if we're autobuilding, let the precompiler take care of the delta - if (file.getName().equals(AdtConstants.FN_COMPILED_RESOURCE_CLASS) - && ResourcesPlugin.getWorkspace().getDescription().isAutoBuilding() == false) { + if (file.getName().equals(AdtConstants.FN_COMPILED_RESOURCE_CLASS)) { loadAndParseRClass(file.getProject()); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java index 6d15f89..6967e67 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java @@ -72,7 +72,6 @@ public class ProjectResources extends ResourceRepository { private final IProject mProject; - /** * Makes a ProjectResources for a given <var>project</var>. * @param project the project. @@ -288,5 +287,9 @@ public class ProjectResources extends ResourceRepository { mResourceValueMap = resourceValueMap; mResIdValueToNameMap = resIdValueToNameMap; mStyleableValueToNameMap = styleableValueMap; + + // Our resource IDs should now be in sync + setIdsRefreshed(); } + } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java index c014601..edf55cd 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java @@ -26,6 +26,8 @@ import com.android.ide.eclipse.adt.internal.resources.ResourceHelper; import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IProjectListener; import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IRawDeltaListener; import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IResourceEventListener; +import com.android.ide.eclipse.adt.internal.sdk.ProjectState; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.ide.eclipse.adt.io.IFileWrapper; import com.android.ide.eclipse.adt.io.IFolderWrapper; import com.android.io.FolderWrapper; @@ -337,10 +339,10 @@ public final class ResourceManager { return; } - // checks if the file is under res/something. + // checks if the file is under res/something or bin/res/something IPath path = file.getFullPath(); - if (path.segmentCount() == 4) { + if (path.segmentCount() == 4 || path.segmentCount() == 5) { if (isInResFolder(path)) { IContainer container = file.getParent(); if (container instanceof IFolder) { @@ -464,6 +466,50 @@ public final class ResourceManager { } /** + * Checks the ResourceRepositories associated with the given project and its dependencies + * and returns whether or not a resource regeneration is needed for that project + * @param project the project to check + * @return true if the project or any of its dependencies says it has new or deleted resources + */ + public boolean projectNeedsIdGeneration(IProject project) { + // Get a list of repositories to check through + List<ProjectResources> repositories = getAllProjectResourcesAssociatedWith(project); + for (ProjectResources repository : repositories) { + if (repository.needsIdRefresh()) { + return true; + } + } + // If we've gotten to here, all repositories are in sync, return false + return false; + } + + /** + * Get all the resource repositories representing this project and any included libraries + * @param project the project to get along with its dependencies + * @return a list of all ProjectResources ordered lowest to highest priority that need to be + * included in this project. + */ + private List<ProjectResources> getAllProjectResourcesAssociatedWith(IProject project) { + List<ProjectResources> toRet = new ArrayList<ProjectResources>(); + // if the project contains libraries, we need to add the libraries resources here + if (project != null) { + ProjectState state = Sdk.getProjectState(project); + if (state != null) { + List<IProject> libraries = state.getFullLibraryProjects(); + for (IProject library : libraries) { + ProjectResources libRes = mMap.get(library); + if (libRes != null) { + toRet.add(libRes); + } + } + } + } + // Add the queried current project last + toRet.add(mMap.get(project)); + return toRet; + } + + /** * Initial project parsing to gather resource info. * @param project */ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlAutoEditStrategyTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlAutoEditStrategyTest.java index ec0e73f..31e3ddb 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlAutoEditStrategyTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlAutoEditStrategyTest.java @@ -27,6 +27,7 @@ import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.ide.IDE; +@SuppressWarnings("javadoc") public class AndroidXmlAutoEditStrategyTest extends AdtProjectTest { public void checkInsertNewline(String before, String after) throws Exception { @@ -101,12 +102,14 @@ public class AndroidXmlAutoEditStrategyTest extends AdtProjectTest { public void testCornerCase2() throws Exception { checkInsertNewline( "\n^", + "\n\n^"); } public void testCornerCase3() throws Exception { checkInsertNewline( " ^", + " \n" + " ^"); } @@ -114,6 +117,7 @@ public class AndroidXmlAutoEditStrategyTest extends AdtProjectTest { public void testSimpleIndentation1() throws Exception { checkInsertNewline( " ^ ", + " \n" + " ^ "); } @@ -122,6 +126,7 @@ public class AndroidXmlAutoEditStrategyTest extends AdtProjectTest { checkInsertNewline( "\n" + " foo^\n", + "\n" + " foo\n" + " ^\n"); @@ -131,6 +136,7 @@ public class AndroidXmlAutoEditStrategyTest extends AdtProjectTest { checkInsertNewline( "\n" + " <newtag>^\n", + "\n" + " <newtag>\n" + " ^\n"); @@ -140,6 +146,7 @@ public class AndroidXmlAutoEditStrategyTest extends AdtProjectTest { checkInsertNewline( "\n" + " <newtag/>^\n", + "\n" + " <newtag/>\n" + " ^\n"); @@ -158,6 +165,7 @@ public class AndroidXmlAutoEditStrategyTest extends AdtProjectTest { checkInsertNewline( "\n" + " <newtag ^attribute='value'/>\n", + "\n" + " <newtag \n" + " ^attribute='value'/>\n"); @@ -167,6 +175,7 @@ public class AndroidXmlAutoEditStrategyTest extends AdtProjectTest { // Make sure that inside a comment we ignore tags etc checkInsertNewline( "<!--\n foo^\n--->\n", + "<!--\n foo\n ^\n--->\n"); } @@ -177,6 +186,7 @@ public class AndroidXmlAutoEditStrategyTest extends AdtProjectTest { "<!--\n" + "<foo><^\n" + "-->\n", + "\n" + "<!--\n" + "<foo><\n" + @@ -188,6 +198,7 @@ public class AndroidXmlAutoEditStrategyTest extends AdtProjectTest { checkInsertNewline( "\n" + " <item>^</item>\n", + "\n" + " <item>\n" + " ^\n" + @@ -201,6 +212,7 @@ public class AndroidXmlAutoEditStrategyTest extends AdtProjectTest { "\n" + " <foo\n" + " name='value'>^</foo>\n", + "\n" + " <foo\n" + " name='value'>\n" + @@ -214,6 +226,7 @@ public class AndroidXmlAutoEditStrategyTest extends AdtProjectTest { "\n" + " <foo\n" + " name='value'/>^\n", + "\n" + " <foo\n" + " name='value'/>\n" + @@ -225,6 +238,7 @@ public class AndroidXmlAutoEditStrategyTest extends AdtProjectTest { "\n" + " <foo\n" + " name='value'></foo>^\n", + "\n" + " <foo\n" + " name='value'></foo>\n" + @@ -243,6 +257,7 @@ public class AndroidXmlAutoEditStrategyTest extends AdtProjectTest { "\n" + " <foo\n" + " name='value'><bar></bar><baz/></foo>^\n", + "\n" + " <foo\n" + " name='value'><bar></bar><baz/></foo>\n" + @@ -257,6 +272,7 @@ public class AndroidXmlAutoEditStrategyTest extends AdtProjectTest { " android:layout_height=\"wrap_content\"\n" + " android:text=\"Button\" >^\n" + " </Button>\n", + " <Button\n" + " android:id=\"@+id/button1\"\n" + " android:layout_width=\"wrap_content\"\n" + @@ -270,12 +286,137 @@ public class AndroidXmlAutoEditStrategyTest extends AdtProjectTest { checkInsertNewline( " <Button\n" + " attr=\"value\"></Button>^\n", + " <Button\n" + " attr=\"value\"></Button>\n" + " ^\n" + ""); } + public void testLineBeginning1() throws Exception { + // Test that if you insert on a blank line, we just add a newline and indent + checkInsertNewline( + "<foo>\n" + + "^\n" + + "</foo>", + + "<foo>\n" + + "\n" + + " ^\n" + + "</foo>"); + } + + public void testLineBeginning2() throws Exception { + // Test that if you insert with the caret on the beginning of a line that has + // content, we insert an indent correctly + checkInsertNewline( + "<foo>\n" + + "^ <bar/>\n" + + "</foo>", + + "<foo>\n" + + "\n" + + " ^<bar/>\n" + + "</foo>"); + } + + public void testLineBeginning3() throws Exception { + checkInsertNewline( + "<foo>\n" + + " <bar>\n" + + "^\n" + + " <baz/>\n" + + " </bar>\n" + + "</foo>", + + "<foo>\n" + + " <bar>\n" + + "\n" + + " ^\n" + + " <baz/>\n" + + " </bar>\n" + + "</foo>"); + + } + + public void testLineBeginning4() throws Exception { + // Test that if you insert with the caret on the beginning of a line that has + // content, we insert an indent correctly + checkInsertNewline( + "<foo>\n" + + " <bar>\n" + + "\n" + + "^ <baz/>\n" + + " </bar>\n" + + "</foo>", + + "<foo>\n" + + " <bar>\n" + + "\n" + + "\n" + + " ^<baz/>\n" + + " </bar>\n" + + "</foo>"); + } + + public void testLineBeginning5() throws Exception { + // Test that if you insert with the caret on the beginning of a line that has + // content, we insert an indent correctly + checkInsertNewline( + "<foo>\n" + + " <bar>\n" + + "\n" + + " ^ <baz/>\n" + + " </bar>\n" + + "</foo>", + + "<foo>\n" + + " <bar>\n" + + "\n" + + " \n" + + " ^<baz/>\n" + + " </bar>\n" + + "</foo>"); + } + + public void testLineBeginning6() throws Exception { + + checkInsertNewline( + " <foo>\n" + + " <bar>\n" + + " \n" + + " \n" + + "^ </bar>\n" + + " </foo>\n", + + " <foo>\n" + + " <bar>\n" + + " \n" + + " \n" + + "\n" + + " ^</bar>\n" + + " </foo>\n"); + } + + public void testBlankContinuation() throws Exception { + + checkInsertNewline( + " <foo>\n" + + " <bar>\n" + + " ^\n" + + " </bar>\n" + + " </foo>\n" + + "", + + " <foo>\n" + + " <bar>\n" + + " \n" + + " ^\n" + + " </bar>\n" + + " </foo>\n" + + ""); + } + /** * To test * When you press / after < I should reindent the current line. For example, diff --git a/files/ant/build.xml b/files/ant/build.xml index ca4cfc9..638eb3c 100644 --- a/files/ant/build.xml +++ b/files/ant/build.xml @@ -545,7 +545,8 @@ androidjar="${android.jar}" rfolder="${gen.absolute.dir}" projectLibrariesResName="project.libraries.res" - projectLibrariesPackageName="project.libraries.package"> + projectLibrariesPackageName="project.libraries.package" + restricttouchedextensionsto="xml"> <res path="${resource.absolute.dir}" /> </aapt> </do-only-if-manifest-hasCode> diff --git a/hierarchyviewer/src/com/android/hierarchyviewer/HierarchyViewer.java b/hierarchyviewer/src/com/android/hierarchyviewer/HierarchyViewer.java index 59ce67f..e945987 100644 --- a/hierarchyviewer/src/com/android/hierarchyviewer/HierarchyViewer.java +++ b/hierarchyviewer/src/com/android/hierarchyviewer/HierarchyViewer.java @@ -27,6 +27,12 @@ public class HierarchyViewer { private static final CharSequence OS_WINDOWS = "Windows"; private static final CharSequence OS_MACOSX = "Mac OS X"; + private static boolean sProfilingEnabled = true; + + public static boolean isProfilingEnabled() { + return sProfilingEnabled; + } + private static void initUserInterface() { System.setProperty("apple.laf.useScreenMenuBar", "true"); System.setProperty("apple.awt.brushMetalLook", "true"); @@ -52,6 +58,10 @@ public class HierarchyViewer { } public static void main(String[] args) { + if (args.length > 0) { + sProfilingEnabled = !args[0].equalsIgnoreCase("-profiling=false"); + } + initUserInterface(); DeviceBridge.initDebugBridge(); diff --git a/hierarchyviewer/src/com/android/hierarchyviewer/scene/ProfilesLoader.java b/hierarchyviewer/src/com/android/hierarchyviewer/scene/ProfilesLoader.java index b91db79..4b9d524 100644 --- a/hierarchyviewer/src/com/android/hierarchyviewer/scene/ProfilesLoader.java +++ b/hierarchyviewer/src/com/android/hierarchyviewer/scene/ProfilesLoader.java @@ -17,6 +17,7 @@ package com.android.hierarchyviewer.scene; import com.android.ddmlib.IDevice; +import com.android.hierarchyviewer.HierarchyViewer; import com.android.hierarchyviewer.device.Window; import com.android.hierarchyviewer.device.DeviceBridge; @@ -30,6 +31,10 @@ import java.io.InputStreamReader; public class ProfilesLoader { public static double[] loadProfiles(IDevice device, Window window, String params) { + if (!HierarchyViewer.isProfilingEnabled()) { + return new double[] { 0.0, 0.0, 0.0 }; + } + Socket socket = null; BufferedReader in = null; BufferedWriter out = null; diff --git a/ide_common/src/com/android/ide/common/resources/IdGeneratingResourceFile.java b/ide_common/src/com/android/ide/common/resources/IdGeneratingResourceFile.java index fa8d0e7..6706715 100644 --- a/ide_common/src/com/android/ide/common/resources/IdGeneratingResourceFile.java +++ b/ide_common/src/com/android/ide/common/resources/IdGeneratingResourceFile.java @@ -31,6 +31,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Set; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; @@ -88,8 +89,8 @@ public final class IdGeneratingResourceFile extends ResourceFile @Override protected void update() { - // remove this file from all existing ResourceItem. - getFolder().getRepository().removeFile(mResourceTypeList, this); + // Copy the previous list of ID names + Set<String> oldIdNames = mIdResources.keySet(); // reset current content. mIdResources.clear(); @@ -97,14 +98,21 @@ public final class IdGeneratingResourceFile extends ResourceFile // need to parse the file and find the IDs. parseFileForIds(); - // Notify the repository about any changes - updateResourceItems(); + // We only need to update the repository if our IDs have changed + if (oldIdNames.equals(mIdResources.keySet()) == false) { + updateResourceItems(); + } } @Override protected void dispose() { + ResourceRepository repository = getRepository(); + // Remove declarations from this file from the repository - getFolder().getRepository().removeFile(mResourceTypeList, this); + repository.removeFile(mResourceTypeList, this); + + // Ask for an ID refresh since we'll be taking away ID generating items + repository.markForIdRefresh(); } @Override @@ -155,6 +163,9 @@ public final class IdGeneratingResourceFile extends ResourceFile private void updateResourceItems() { ResourceRepository repository = getRepository(); + // remove this file from all existing ResourceItem. + repository.removeFile(mResourceTypeList, this); + // First add this as a layout file ResourceItem item = repository.getResourceItem(mFileType, mFileName); item.add(this); @@ -165,6 +176,9 @@ public final class IdGeneratingResourceFile extends ResourceFile // add this file to the list of files generating ID resources. item.add(this); } + + // Ask the repository for an ID refresh + repository.markForIdRefresh(); } /** diff --git a/ide_common/src/com/android/ide/common/resources/MultiResourceFile.java b/ide_common/src/com/android/ide/common/resources/MultiResourceFile.java index 6d8ca0a..b3e35d9 100644 --- a/ide_common/src/com/android/ide/common/resources/MultiResourceFile.java +++ b/ide_common/src/com/android/ide/common/resources/MultiResourceFile.java @@ -54,6 +54,10 @@ public final class MultiResourceFile extends ResourceFile implements IValueResou super(file, folder); } + // Boolean flag to track whether a named element has been added or removed, thus requiring + // a new ID table to be generated + private boolean mNeedIdRefresh; + @Override protected void load() { // need to parse the file and find the content. @@ -62,14 +66,21 @@ public final class MultiResourceFile extends ResourceFile implements IValueResou // create new ResourceItems for the new content. mResourceTypeList = Collections.unmodifiableCollection(mResourceItems.keySet()); + // We need an ID generation step + mNeedIdRefresh = true; + // create/update the resource items. updateResourceItems(); } @Override protected void update() { - // remove this file from all existing ResourceItem. - getFolder().getRepository().removeFile(mResourceTypeList, this); + // Reset the ID generation flag + mNeedIdRefresh = false; + + // Copy the previous version of our list of ResourceItems and types + Map<ResourceType, Map<String, ResourceValue>> oldResourceItems + = new EnumMap<ResourceType, Map<String, ResourceValue>>(mResourceItems); // reset current content. mResourceItems.clear(); @@ -80,14 +91,34 @@ public final class MultiResourceFile extends ResourceFile implements IValueResou // create new ResourceItems for the new content. mResourceTypeList = Collections.unmodifiableCollection(mResourceItems.keySet()); + // Check to see if any names have changed. If so, mark the flag so updateResourceItems + // can notify the ResourceRepository that an ID refresh is needed + if (oldResourceItems.keySet().equals(mResourceItems.keySet())) { + for (ResourceType type : mResourceTypeList) { + // We just need to check the names of the items. + // If there are new or removed names then we'll have to regenerate IDs + if (mResourceItems.get(type).keySet() + .equals(oldResourceItems.get(type).keySet()) == false) { + mNeedIdRefresh = true; + } + } + } else { + // If our type list is different, obviously the names will be different + mNeedIdRefresh = true; + } // create/update the resource items. updateResourceItems(); } @Override protected void dispose() { + ResourceRepository repository = getRepository(); + // only remove this file from all existing ResourceItem. - getFolder().getRepository().removeFile(mResourceTypeList, this); + repository.removeFile(mResourceTypeList, this); + + // We'll need an ID refresh because we deleted items + repository.markForIdRefresh(); // don't need to touch the content, it'll get reclaimed as this objects disappear. // In the mean time other objects may need to access it. @@ -106,6 +137,10 @@ public final class MultiResourceFile extends ResourceFile implements IValueResou private void updateResourceItems() { ResourceRepository repository = getRepository(); + + // remove this file from all existing ResourceItem. + repository.removeFile(mResourceTypeList, this); + for (ResourceType type : mResourceTypeList) { Map<String, ResourceValue> list = mResourceItems.get(type); @@ -119,6 +154,11 @@ public final class MultiResourceFile extends ResourceFile implements IValueResou } } } + + // If we need an ID refresh, ask the repository for that now + if (mNeedIdRefresh) { + repository.markForIdRefresh(); + } } /** diff --git a/ide_common/src/com/android/ide/common/resources/ResourceRepository.java b/ide_common/src/com/android/ide/common/resources/ResourceRepository.java index fa533cb..4af4a1a 100644 --- a/ide_common/src/com/android/ide/common/resources/ResourceRepository.java +++ b/ide_common/src/com/android/ide/common/resources/ResourceRepository.java @@ -68,6 +68,7 @@ public abstract class ResourceRepository { protected final IntArrayWrapper mWrapper = new IntArrayWrapper(null); + private boolean mNeedsIdRefresh; /** * Makes a resource repository @@ -195,6 +196,29 @@ public abstract class ResourceRepository { protected abstract ResourceItem createResourceItem(String name); /** + * Sets a flag which determines whether aapt needs to be run to regenerate resource IDs + */ + protected void markForIdRefresh() { + mNeedsIdRefresh = true; + } + + /** + * Returns whether this repository has been marked as "dirty"; if one or more of the constituent + * files have declared that the resource item names that they provide have changed. + */ + public boolean needsIdRefresh() { + return mNeedsIdRefresh; + } + + /** + * Indicates that the resources IDs have been regenerated, so the repository is now in a clean + * state + */ + public void setIdsRefreshed() { + mNeedsIdRefresh = false; + } + + /** * Processes a folder and adds it to the list of existing folders. * @param folder the folder to process * @return the ResourceFolder created from this folder, or null if the process failed. diff --git a/ide_common/src/com/android/ide/common/resources/SingleResourceFile.java b/ide_common/src/com/android/ide/common/resources/SingleResourceFile.java index 9c8977e..b589b35 100644 --- a/ide_common/src/com/android/ide/common/resources/SingleResourceFile.java +++ b/ide_common/src/com/android/ide/common/resources/SingleResourceFile.java @@ -41,8 +41,8 @@ public class SingleResourceFile extends ResourceFile { sParserFactory.setNamespaceAware(true); } - private String mResourceName; - private ResourceType mType; + private final String mResourceName; + private final ResourceType mType; private ResourceValue mValue; public SingleResourceFile(IAbstractFile file, ResourceFolder folder) { @@ -79,6 +79,9 @@ public class SingleResourceFile extends ResourceFile { // add this file to the list of files generating this resource item. item.add(this); + + // Ask for an ID refresh since we're adding an item that will generate an ID + getRepository().markForIdRefresh(); } @Override @@ -92,6 +95,9 @@ public class SingleResourceFile extends ResourceFile { // only remove this file from the existing ResourceItem. getFolder().getRepository().removeFile(mType, this); + // Ask for an ID refresh since we're removing an item that previously generated an ID + getRepository().markForIdRefresh(); + // don't need to touch the content, it'll get reclaimed as this objects disappear. // In the mean time other objects may need to access it. } diff --git a/sdkmanager/app/tests/com/android/sdkmanager/AvdManagerTest.java b/sdkmanager/app/tests/com/android/sdkmanager/AvdManagerTest.java index a5a8289..93b12c1 100644 --- a/sdkmanager/app/tests/com/android/sdkmanager/AvdManagerTest.java +++ b/sdkmanager/app/tests/com/android/sdkmanager/AvdManagerTest.java @@ -19,6 +19,7 @@ package com.android.sdkmanager; import com.android.io.FileWrapper; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.SdkConstants; +import com.android.sdklib.SdkManagerTestCase; import com.android.sdklib.internal.avd.AvdInfo; import com.android.sdklib.internal.project.ProjectProperties; diff --git a/sdkmanager/app/tests/com/android/sdkmanager/MainTest.java b/sdkmanager/app/tests/com/android/sdkmanager/MainTest.java index 4a17e32..ef5a715 100644 --- a/sdkmanager/app/tests/com/android/sdkmanager/MainTest.java +++ b/sdkmanager/app/tests/com/android/sdkmanager/MainTest.java @@ -19,6 +19,7 @@ package com.android.sdkmanager; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.SdkConstants; +import com.android.sdklib.SdkManagerTestCase; import com.android.sdklib.internal.avd.AvdInfo; import com.android.sdklib.repository.SdkAddonConstants; import com.android.sdklib.repository.SdkRepoConstants; diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java index 467529d..b8226c0 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java @@ -16,6 +16,7 @@ package com.android.sdklib; +import com.android.sdklib.SdkManager.LayoutlibVersion; import com.android.sdklib.util.SparseArray; import java.io.File; @@ -45,22 +46,31 @@ final class PlatformTarget implements IAndroidTarget { private String[] mSkins; private String[] mAbis; private boolean mAbiCompatibilityMode; - + private final LayoutlibVersion mLayoutlibVersion; /** * Creates a Platform target. + * * @param sdkOsPath the root folder of the SDK * @param platformOSPath the root folder of the platform component * @param apiLevel the API Level * @param codeName the codename. can be null. * @param versionName the version name of the platform. * @param revision the revision of the platform component. + * @param layoutlibVersion The {@link LayoutlibVersion}. May be null. * @param abis the list of supported abis * @param properties the platform properties */ @SuppressWarnings("deprecation") - PlatformTarget(String sdkOsPath, String platformOSPath, int apiLevel, - String codeName, String versionName, int revision, String[] abis, + PlatformTarget( + String sdkOsPath, + String platformOSPath, + int apiLevel, + String codeName, + String versionName, + int revision, + LayoutlibVersion layoutlibVersion, + String[] abis, Map<String, String> properties) { if (platformOSPath.endsWith(File.separator) == false) { platformOSPath = platformOSPath + File.separator; @@ -70,6 +80,7 @@ final class PlatformTarget implements IAndroidTarget { mVersion = new AndroidVersion(apiLevel, codeName); mVersionName = versionName; mRevision = revision; + mLayoutlibVersion = layoutlibVersion; if (mVersion.isPreview()) { mName = String.format(PLATFORM_NAME_PREVIEW, mVersionName); @@ -123,7 +134,13 @@ final class PlatformTarget implements IAndroidTarget { mAbiCompatibilityMode = true; mAbis = new String[] { SdkConstants.ABI_ARMEABI }; } + } + /** + * Returns the {@link LayoutlibVersion}. May be null. + */ + public LayoutlibVersion getLayoutlibVersion() { + return mLayoutlibVersion; } /** @@ -151,9 +168,9 @@ final class PlatformTarget implements IAndroidTarget { return mRootFolderOsPath; } - /* - * (non-Javadoc) - * + /** + * {@inheritDoc} + * <p/> * For Platform, the vendor name is always "Android". * * @see com.android.sdklib.IAndroidTarget#getVendor() diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java index 43e9ac9..987ae21 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java @@ -85,8 +85,35 @@ public class SdkManager { /** The location of the SDK as an OS path */ private final String mOsSdkPath; - /** Valid targets that have been loaded. */ - private IAndroidTarget[] mTargets; + /** Valid targets that have been loaded. Can be empty but not null. */ + private IAndroidTarget[] mTargets = new IAndroidTarget[0]; + + public static class LayoutlibVersion implements Comparable<LayoutlibVersion> { + private final int mApi; + private final int mRevision; + + public static final int NOT_SPECIFIED = 0; + + public LayoutlibVersion(int api, int revision) { + mApi = api; + mRevision = revision; + } + + public int getApi() { + return mApi; + } + + public int getRevision() { + return mRevision; + } + + public int compareTo(LayoutlibVersion rhs) { + boolean useRev = this.mRevision > NOT_SPECIFIED && rhs.mRevision > NOT_SPECIFIED; + int lhsValue = (this.mApi << 16) + (useRev ? this.mRevision : 0); + int rhsValue = (rhs.mApi << 16) + (useRev ? rhs.mRevision : 0); + return lhsValue - rhsValue; + } + } /** * Create a new {@link SdkManager} instance. @@ -232,6 +259,39 @@ public class SdkManager { } /** + * Returns the greatest {@link LayoutlibVersion} found amongst all platform + * targets currently loaded in the SDK. + * <p/> + * We only started recording Layoutlib Versions recently in the platform meta data + * so it's possible to have an SDK with many platforms loaded but no layoutlib + * version defined. + * + * @return The greatest {@link LayoutlibVersion} or null if none is found. + * @deprecated This helper method is provisional. I am marking it as deprecated for + * lack of a better tag (e.g. "@future"?). It's deprecated in the sense that + * we're not using it yet and should NOT be considered a stable API yet. + * We'll probably need to revisit it when the want to actually use it. + * If it's convenient as-is then this deprecation message shall be removed. + */ + @Deprecated + public LayoutlibVersion getMaxLayoutlibVersion() { + LayoutlibVersion maxVersion = null; + + for (IAndroidTarget target : getTargets()) { + if (target instanceof PlatformTarget) { + LayoutlibVersion lv = ((PlatformTarget) target).getLayoutlibVersion(); + if (lv != null) { + if (maxVersion == null || lv.compareTo(maxVersion) > 0) { + maxVersion = lv; + } + } + } + } + + return maxVersion; + } + + /** * Loads the Platforms from the SDK. * Creates the "platforms" folder if necessary. * @@ -331,18 +391,40 @@ public class SdkManager { // codename is irrelevant at this point. } - // platform rev number + // platform rev number & layoutlib version are extracted from the source.properties + // saved by the SDK Manager when installing the package. + int revision = 1; - FileWrapper sourcePropFile = new FileWrapper(platformFolder, - SdkConstants.FN_SOURCE_PROP); + LayoutlibVersion layoutlibVersion = null; + + FileWrapper sourcePropFile = + new FileWrapper(platformFolder, SdkConstants.FN_SOURCE_PROP); + Map<String, String> sourceProp = ProjectProperties.parsePropertyFile( sourcePropFile, log); + if (sourceProp != null) { try { revision = Integer.parseInt(sourceProp.get("Pkg.Revision")); //$NON-NLS-1$ } catch (NumberFormatException e) { // do nothing, we'll keep the default value of 1. } + + try { + String propApi = sourceProp.get("Layoutlib.Api"); //$NON-NLS-1$ + String propRev = sourceProp.get("Layoutlib.Revision"); //$NON-NLS-1$ + int llApi = propApi == null ? LayoutlibVersion.NOT_SPECIFIED : + Integer.parseInt(propApi); + int llRev = propRev == null ? LayoutlibVersion.NOT_SPECIFIED : + Integer.parseInt(propRev); + if (llApi > LayoutlibVersion.NOT_SPECIFIED && + llRev >= LayoutlibVersion.NOT_SPECIFIED) { + layoutlibVersion = new LayoutlibVersion(llApi, llRev); + } + } catch (NumberFormatException e) { + // do nothing, we'll ignore the layoutlib version if it's invalid + } + map.putAll(sourceProp); } @@ -371,6 +453,7 @@ public class SdkManager { apiCodename, apiName, revision, + layoutlibVersion, abiList, map); diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/LayoutlibVersionTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/LayoutlibVersionTest.java new file mode 100755 index 0000000..0c197aa --- /dev/null +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/LayoutlibVersionTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2011 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.sdklib; + +import com.android.sdklib.SdkManager.LayoutlibVersion; + +import junit.framework.TestCase; + +/** + * Unit test for {@link LayoutlibVersion}. + */ +public class LayoutlibVersionTest extends TestCase { + + public void testLayoutlibVersion_create() { + LayoutlibVersion lv = new LayoutlibVersion(1, 2); + assertEquals(1, lv.getApi()); + assertEquals(2, lv.getRevision()); + } + + public void testLayoutlibVersion_compare() { + assertTrue(new LayoutlibVersion(1, 1).compareTo(new LayoutlibVersion(1, 1)) == 0); + assertTrue(new LayoutlibVersion(1, 2).compareTo(new LayoutlibVersion(1, 1)) > 0); + assertTrue(new LayoutlibVersion(1, 1).compareTo(new LayoutlibVersion(1, 2)) < 0); + assertTrue(new LayoutlibVersion(2, 2).compareTo(new LayoutlibVersion(1, 3)) > 0); + + // the lack of an API (== 0) naturally sorts as the lowest value possible. + assertTrue(new LayoutlibVersion(0, 1).compareTo(new LayoutlibVersion(0, 2)) < 0); + assertTrue(new LayoutlibVersion(0, 1).compareTo(new LayoutlibVersion(1, 2)) < 0); + assertTrue(new LayoutlibVersion(0, 3).compareTo(new LayoutlibVersion(1, 2)) < 0); + assertTrue(new LayoutlibVersion(1, 2).compareTo(new LayoutlibVersion(0, 3)) > 0); + + // if we lack the revision number, we don't use it in comparison + assertTrue(new LayoutlibVersion(2, 0).compareTo(new LayoutlibVersion(2, 2)) == 0); + assertTrue(new LayoutlibVersion(2, 2).compareTo(new LayoutlibVersion(2, 0)) == 0); + } + +} diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/SdkManagerTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/SdkManagerTest.java new file mode 100755 index 0000000..bf8600c --- /dev/null +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/SdkManagerTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.sdklib; + + +import com.android.sdklib.SdkManager.LayoutlibVersion; + +public class SdkManagerTest extends SdkManagerTestCase { + + @Override + public void setUp() throws Exception { + super.setUp(); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + } + + public void testSdkManager_LayoutlibVersion() { + SdkManager sdkman = getSdkManager(); + IAndroidTarget t = sdkman.getTargets()[0]; + + assertTrue(t instanceof PlatformTarget); + + LayoutlibVersion lv = ((PlatformTarget) t).getLayoutlibVersion(); + assertNotNull(lv); + assertEquals(5, lv.getApi()); + assertEquals(2, lv.getRevision()); + + assertSame(lv, sdkman.getMaxLayoutlibVersion()); + } +} diff --git a/sdkmanager/app/tests/com/android/sdkmanager/SdkManagerTestCase.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/SdkManagerTestCase.java index 9fdd852..e4feacf 100755 --- a/sdkmanager/app/tests/com/android/sdkmanager/SdkManagerTestCase.java +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/SdkManagerTestCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.sdkmanager; +package com.android.sdklib; import com.android.prefs.AndroidLocation; @@ -126,8 +126,10 @@ public abstract class SdkManagerTestCase extends TestCase { */ private File makeFakeSdk() throws IOException { + // First we create a temp file to "reserve" the temp directory name we want to use. File tmpFile = File.createTempFile( this.getClass().getSimpleName() + '_' + this.getName(), null); + // Then erase the file and make the directory tmpFile.delete(); tmpFile.mkdirs(); @@ -145,15 +147,24 @@ public abstract class SdkManagerTestCase extends TestCase { targetDir.mkdirs(); new File(targetDir, SdkConstants.FN_FRAMEWORK_LIBRARY).createNewFile(); new File(targetDir, SdkConstants.FN_FRAMEWORK_AIDL).createNewFile(); - new File(targetDir, SdkConstants.FN_SOURCE_PROP).createNewFile(); + + File sourceProp = new File(targetDir, SdkConstants.FN_SOURCE_PROP); + sourceProp.createNewFile(); + FileWriter out = new FileWriter(sourceProp); + out.write("Layoutlib.Api=5\n"); + out.write("Layoutlib.Revision=2\n"); + out.close(); + File buildProp = new File(targetDir, SdkConstants.FN_BUILD_PROP); - FileWriter out = new FileWriter(buildProp); + out = new FileWriter(buildProp); out.write(SdkManager.PROP_VERSION_RELEASE + "=0.0\n"); out.write(SdkManager.PROP_VERSION_SDK + "=0\n"); out.write(SdkManager.PROP_VERSION_CODENAME + "=REL\n"); out.close(); + File imagesDir = new File(targetDir, "images"); imagesDir.mkdirs(); + new File(imagesDir, "userdata.img").createNewFile(); File skinsDir = new File(targetDir, "skins"); File hvgaDir = new File(skinsDir, "HVGA"); diff --git a/traceview/src/com/android/traceview/DmTraceReader.java b/traceview/src/com/android/traceview/DmTraceReader.java index ac44a09..d75ba8c 100644 --- a/traceview/src/com/android/traceview/DmTraceReader.java +++ b/traceview/src/com/android/traceview/DmTraceReader.java @@ -20,8 +20,8 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.FileReader; import java.io.IOException; +import java.io.InputStreamReader; import java.nio.BufferUnderflowException; import java.nio.ByteOrder; import java.nio.MappedByteBuffer; @@ -404,7 +404,8 @@ public class DmTraceReader extends TraceReader { long parseKeys() throws IOException { BufferedReader in = null; try { - in = new BufferedReader(new FileReader(mTraceFileName)); + in = new BufferedReader(new InputStreamReader( + new FileInputStream(mTraceFileName), "US-ASCII")); } catch (FileNotFoundException ex) { System.err.println(ex.getMessage()); } |