diff options
101 files changed, 2963 insertions, 456 deletions
diff --git a/anttasks/src/com/android/ant/AaptExecTask.java b/anttasks/src/com/android/ant/AaptExecTask.java index 610ce0f..e3bb0a2 100644 --- a/anttasks/src/com/android/ant/AaptExecTask.java +++ b/anttasks/src/com/android/ant/AaptExecTask.java @@ -487,7 +487,7 @@ public final class AaptExecTask extends SingleDependencyTask { } // filters if needed - if (mResourceFilter != null) { + if (mResourceFilter != null && mResourceFilter.length() > 0) { task.createArg().setValue("-c"); task.createArg().setValue(mResourceFilter); } @@ -511,7 +511,7 @@ public final class AaptExecTask extends SingleDependencyTask { } } - if (extraPackages != null) { + if (extraPackages != null && extraPackages.length() > 0) { task.createArg().setValue("--extra-packages"); task.createArg().setValue(extraPackages); } @@ -526,13 +526,13 @@ public final class AaptExecTask extends SingleDependencyTask { task.createArg().setValue(Integer.toString(mVersionCode)); } - if ((mVersionName != null) && (mVersionName.length() > 0)) { + if (mVersionName != null && mVersionName.length() > 0) { task.createArg().setValue("--version-name"); task.createArg().setValue(mVersionName); } // manifest location - if (mManifest != null) { + if (mManifest != null && mManifest.length() > 0) { task.createArg().setValue("-M"); task.createArg().setValue(mManifest); } diff --git a/anttasks/src/com/android/ant/NewSetupTask.java b/anttasks/src/com/android/ant/NewSetupTask.java index b8765da..f3fd9aa 100644 --- a/anttasks/src/com/android/ant/NewSetupTask.java +++ b/anttasks/src/com/android/ant/NewSetupTask.java @@ -528,6 +528,8 @@ public class NewSetupTask extends Task { System.out.println("------------------\n"); + boolean hasLibraries = jarsPath.list().length > 0; + if (androidTarget.getVersion().getApiLevel() <= 15) { System.out.println("API<=15: Adding annotations.jar to the classpath.\n"); @@ -545,7 +547,7 @@ public class NewSetupTask extends Task { antProject.addReference(mProjectLibrariesLibsOut, libsPath); // the rest is done only if there's a library. - if (jarsPath.list().length > 0) { + if (hasLibraries) { antProject.addReference(mProjectLibrariesRootOut, rootPath); antProject.addReference(mProjectLibrariesResOut, resPath); antProject.setProperty(mProjectLibrariesPackageOut, packageStrBuilder.toString()); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java index 4b7fbe0..6da978d 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java @@ -669,7 +669,7 @@ public class Hyperlinks { private static FolderConfiguration getConfiguration() { IEditorPart editor = getEditor(); if (editor != null) { - LayoutEditorDelegate delegate = (LayoutEditorDelegate) editor; + LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor(editor); GraphicalEditorPart graphicalEditor = delegate == null ? null : delegate.getGraphicalEditor(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleWizard.java index 2a55395..cf6e967 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleWizard.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleWizard.java @@ -79,7 +79,7 @@ class ExtractStyleWizard extends VisualRefactoringWizard { private Button mRemoveExtracted; private Button mSetStyle; private Button mRemoveAll; - private Button mExtend;; + private Button mExtend; private CheckboxTableViewer mCheckedView; private String mParentStyle; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java index e3669f0..b3174d3 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java @@ -35,7 +35,6 @@ import com.android.tools.lint.checks.ObsoleteLayoutParamsDetector; import com.android.tools.lint.checks.OverdrawDetector; import com.android.tools.lint.checks.StringFormatDetector; import com.android.tools.lint.checks.TranslationDetector; -import com.android.tools.lint.checks.WrongIdDetector; import org.eclipse.core.resources.IMarker; import org.eclipse.core.runtime.CoreException; @@ -183,9 +182,7 @@ class AddSuppressAttribute implements ICompletionProposal { || id.equals(StringFormatDetector.ARG_TYPES.getId()) || id.equals(StringFormatDetector.ARG_COUNT.getId()) || id.equals(TranslationDetector.MISSING.getId()) - || id.equals(TranslationDetector.EXTRA.getId()) - || id.equals(WrongIdDetector.UNKNOWN_ID.getId()) - || id.equals(WrongIdDetector.UNKNOWN_ID_LAYOUT.getId())) { + || id.equals(TranslationDetector.EXTRA.getId())) { node = document.getDocumentElement(); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java index 8c8bea7..3289bbe 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java @@ -124,7 +124,7 @@ public class EclipseLintClient extends LintClient implements IDomParser { // ----- Extends LintClient ----- @Override - public void log(Throwable exception, String format, Object... args) { + public void log(Severity severity, Throwable exception, String format, Object... args) { if (exception == null) { AdtPlugin.log(IStatus.WARNING, format, args); } else { @@ -261,7 +261,7 @@ public class EclipseLintClient extends LintClient implements IDomParser { } } - if (s == Severity.ERROR) { + if (s == Severity.FATAL) { mWasFatal = true; } } @@ -369,6 +369,7 @@ public class EclipseLintClient extends LintClient implements IDomParser { return IMarker.SEVERITY_INFO; case WARNING: return IMarker.SEVERITY_WARNING; + case FATAL: case ERROR: default: return IMarker.SEVERITY_ERROR; @@ -821,6 +822,7 @@ public class EclipseLintClient extends LintClient implements IDomParser { private class LocationHandle implements Handle { private File mFile; private lombok.ast.Node mNode; + private Object mClientData; public LocationHandle(File file, lombok.ast.Node node) { mFile = file; @@ -832,6 +834,17 @@ public class EclipseLintClient extends LintClient implements IDomParser { lombok.ast.Position pos = mNode.getPosition(); return Location.create(mFile, null /*contents*/, pos.getStart(), pos.getEnd()); } + + @Override + public void setClientData(@Nullable Object clientData) { + mClientData = clientData; + } + + @Override + @Nullable + public Object getClientData() { + return mClientData; + } } } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ProjectLintConfiguration.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ProjectLintConfiguration.java index 671dd67..77cd115 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ProjectLintConfiguration.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ProjectLintConfiguration.java @@ -81,7 +81,7 @@ class ProjectLintConfiguration extends DefaultConfiguration { @Override public Severity getSeverity(Issue issue) { Severity severity = super.getSeverity(issue); - if (mFatalOnly && severity != Severity.ERROR) { + if (mFatalOnly && severity != Severity.FATAL) { return Severity.IGNORE; } return severity; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java index f52d719..bd2423e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java @@ -147,7 +147,7 @@ public class LintPreferencePage extends PropertyPage implements IWorkbenchPrefer mCheckExportCheckbox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1)); mCheckExportCheckbox.setSelection(true); - mCheckExportCheckbox.setText("Run full error check when exporting app"); + mCheckExportCheckbox.setText("Run full error check when exporting app and abort if fatal errors are found"); Label separator = new Label(container, SWT.SEPARATOR | SWT.HORIZONTAL); separator.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 2, 1)); @@ -227,7 +227,7 @@ public class LintPreferencePage extends PropertyPage implements IWorkbenchPrefer mSeverityCombo = new Combo(container, SWT.READ_ONLY); mSeverityCombo.setItems(new String[] { - "(Default)", "Error", "Warning", "Information", "Ignore" + "(Default)", "Fatal", "Error", "Warning", "Information", "Ignore" }); GridData gdSeverityCombo = new GridData(SWT.FILL, SWT.TOP, false, false, 1, 1); gdSeverityCombo.widthHint = 90; @@ -679,6 +679,7 @@ public class LintPreferencePage extends PropertyPage implements IWorkbenchPrefer ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages(); switch (severity) { + case FATAL: case ERROR: return sharedImages.getImage(ISharedImages.IMG_OBJS_ERROR_TSK); case WARNING: diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizardState.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizardState.java index 6b8a1fb..33f9884 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizardState.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizardState.java @@ -215,7 +215,7 @@ public class NewProjectWizardState { } if (newPackageName != null && newPackageName.length() > 0) { - packageName = newPackageName;; + packageName = newPackageName; } if (activity != null) { diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger.tests/src/com/android/ide/eclipse/gltrace/format/GLMessageFormatterTest.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger.tests/src/com/android/ide/eclipse/gltrace/format/GLMessageFormatterTest.java index 305e8ac..984cbee 100644 --- a/eclipse/plugins/com.android.ide.eclipse.gldebugger.tests/src/com/android/ide/eclipse/gltrace/format/GLMessageFormatterTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger.tests/src/com/android/ide/eclipse/gltrace/format/GLMessageFormatterTest.java @@ -39,6 +39,7 @@ public class GLMessageFormatterTest { "const GLchar*, glGetString, GLenum name", "void, glMultMatrixf, const GLfloat* m", "GLenum, eglBindAPI, GLEnum arg", + "void, glGetActiveAttrib, GLenum* type", "void, glTexImage2D, GLint level, GLsizei width, const GLvoid* pixels"); private static GLMessageFormatter sGLMessageFormatter; @@ -126,6 +127,19 @@ public class GLMessageFormatterTest { assertEquals(expected, actual); } + @Test + public void testMessageWithEnumPointer() { + //void, glGetActiveAttrib, GLenum* type + GLMessage msg = constructGLMessage(null, + Function.glGetActiveAttrib, + createIntegerPointerDataType(GLEnum.GL_FLOAT_MAT4.value)); + + String expected = "glGetActiveAttrib(type = [GL_FLOAT_MAT4])"; + String actual = sGLMessageFormatter.formatGLMessage(msg); + + assertEquals(expected, actual); + } + private DataType createStringDataType(String retValue) { return DataType.newBuilder() .addCharValue(ByteString.copyFromUtf8(retValue)) diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.gldebugger/plugin.xml index bcc3871..d693916 100644 --- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/plugin.xml +++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/plugin.xml @@ -19,7 +19,7 @@ category="com.android.ide.eclipse.gltrace"
class="com.android.ide.eclipse.gltrace.views.FrameBufferView"
icon="icons/opengl.png"
- id="com.android.ide.eclipse.gltrace.views.GLFrameBuffer"
+ id="com.android.ide.eclipse.gltrace.views.FrameBuffer"
name="Framebuffer"
restorable="true">
</view>
@@ -33,10 +33,10 @@ </view>
<view
category="com.android.ide.eclipse.gltrace"
- class="com.android.ide.eclipse.gltrace.views.TextureView"
+ class="com.android.ide.eclipse.gltrace.views.DetailsView"
icon="icons/opengl.png"
- id="com.android.ide.eclipse.gltrace.views.Texture"
- name="Texture"
+ id="com.android.ide.eclipse.gltrace.views.Details"
+ name="Details"
restorable="true">
</view>
</extension>
diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/CollectTraceAction.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/CollectTraceAction.java index 04916b5..58e78b4 100644 --- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/CollectTraceAction.java +++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/CollectTraceAction.java @@ -206,9 +206,9 @@ public class CollectTraceAction implements IWorkbenchWindowActionDelegate { } private void disableGLTrace(IDevice device) { - // The only way to disable is by enabling tracing with an empty package name + // The only way to disable is by enabling tracing with an invalid package name try { - setGLTraceOn(device, ""); + setGLTraceOn(device, "no.such.package"); //$NON-NLS-1$ } catch (Exception e) { // ignore any exception } diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/GLTracePerspective.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/GLTracePerspective.java index 71b929b..d8f1a0b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/GLTracePerspective.java +++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/GLTracePerspective.java @@ -16,9 +16,9 @@ package com.android.ide.eclipse.gltrace; +import com.android.ide.eclipse.gltrace.views.DetailsView; import com.android.ide.eclipse.gltrace.views.FrameBufferView; import com.android.ide.eclipse.gltrace.views.StateView; -import com.android.ide.eclipse.gltrace.views.TextureView; import org.eclipse.ui.IFolderLayout; import org.eclipse.ui.IPageLayout; @@ -44,7 +44,7 @@ public class GLTracePerspective implements IPerspectiveFactory { // Add the Texture View in the 3rd column IFolderLayout column3 = layout.createFolder(FB_FOLDER_ID, IPageLayout.RIGHT, 0.6f, STATE_FOLDER_ID); - column3.addView(TextureView.ID); + column3.addView(DetailsView.ID); // Add the OpenGL Framebuffer view below the texture view (bottom of 3rd column) IFolderLayout column3bottom = layout.createFolder(TEXTURE_VIEW_FOLDER_ID, diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/GLFunctionTraceViewer.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/GLFunctionTraceViewer.java index 6eac701..debd086 100644 --- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/GLFunctionTraceViewer.java +++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/GLFunctionTraceViewer.java @@ -24,6 +24,7 @@ import com.android.ide.eclipse.gltrace.editors.GLCallGroups.GLCallNode; import com.android.ide.eclipse.gltrace.model.GLCall; import com.android.ide.eclipse.gltrace.model.GLFrame; import com.android.ide.eclipse.gltrace.model.GLTrace; +import com.android.ide.eclipse.gltrace.views.DetailsPage; import com.android.ide.eclipse.gltrace.views.FrameBufferViewPage; import org.eclipse.core.runtime.IProgressMonitor; @@ -276,7 +277,9 @@ public class GLFunctionTraceViewer extends EditorPart implements ISelectionProvi mDurationMinimap.setCallRangeForCurrentFrame(mCallStartIndex, mCallEndIndex); // update FB view - mFrameBufferViewPage.setSelectedFrame(selectedFrame - 1); + if (mFrameBufferViewPage != null) { + mFrameBufferViewPage.setSelectedFrame(selectedFrame - 1); + } } /** @@ -561,8 +564,7 @@ public class GLFunctionTraceViewer extends EditorPart implements ISelectionProvi return true; } - GLCall call = ((GLCallNode) element).getCall(); - String text = call.getFunction().toString(); + String text = getTextUnderNode((GLCallNode) element); if (mPatterns.size() == 0) { // match if there are no regex filters @@ -579,6 +581,23 @@ public class GLFunctionTraceViewer extends EditorPart implements ISelectionProvi return false; } + + /** Obtain a string representation of all functions under a given tree node. */ + private String getTextUnderNode(GLCallNode element) { + String func = element.getCall().getFunction().toString(); + if (!element.hasChildren()) { + return func; + } + + StringBuilder sb = new StringBuilder(100); + sb.append(func); + + for (GLCallNode child : element.getChildren()) { + sb.append(getTextUnderNode(child)); + } + + return sb.toString(); + } } @Override @@ -626,4 +645,8 @@ public class GLFunctionTraceViewer extends EditorPart implements ISelectionProvi return mFrameBufferViewPage; } + + public DetailsPage getDetailsPage() { + return new DetailsPage(mTrace); + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/format/GLMessageFormatter.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/format/GLMessageFormatter.java index 2b7c491..c0a11fe 100644 --- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/format/GLMessageFormatter.java +++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/format/GLMessageFormatter.java @@ -115,7 +115,7 @@ public class GLMessageFormatter { } private String formatPointer(DataType var, Type typeSpec) { - if (var.getType() != typeSpec) { + if (var.getType() != typeSpec && !isEnumTypeWithIntData(var, typeSpec)) { // the type of the data in the message does not match expected specification. // in such a case, just print the data as a pointer and don't try to interpret it. if (var.getIntValueCount() > 0) { @@ -126,7 +126,7 @@ public class GLMessageFormatter { } // Display as array if possible - switch (var.getType()) { + switch (typeSpec) { case BOOL: return var.getBoolValueList().toString(); case FLOAT: @@ -135,6 +135,15 @@ public class GLMessageFormatter { return var.getIntValueList().toString(); case CHAR: return var.getCharValueList().get(0).toStringUtf8(); + case ENUM: + List<Integer> vals = var.getIntValueList(); + StringBuilder sb = new StringBuilder(vals.size() * 5); + sb.append('['); + for (Integer v: vals) { + sb.append(GLEnum.valueOf(v.intValue())); + } + sb.append(']'); + return sb.toString(); } // We have a pointer, but we don't have the data pointed to. @@ -145,4 +154,8 @@ public class GLMessageFormatter { return String.format("0x%x", var.getIntValue(0)); //$NON-NLS-1$ } } + + private boolean isEnumTypeWithIntData(DataType var, Type typeSpec) { + return var.getType() == Type.INT && typeSpec == Type.ENUM; + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/GLObjectProperty.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/GLObjectProperty.java new file mode 100644 index 0000000..84ad6ec --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/GLObjectProperty.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.gltrace.state; + +import com.google.common.base.Joiner; + +import java.util.List; + +public class GLObjectProperty extends GLAbstractAtomicProperty { + private final Object mDefaultValue; + private Object mCurrentValue; + + private static final Joiner JOINER = Joiner.on(", "); //$NON-NLS-1$ + + public GLObjectProperty(GLStateType type, Object defaultValue) { + super(type); + + mDefaultValue = mCurrentValue = defaultValue; + } + + @Override + public boolean isDefault() { + return mDefaultValue == mCurrentValue; + } + + @Override + public void setValue(Object newValue) { + mCurrentValue = newValue; + } + + @Override + public String getStringValue() { + if (mCurrentValue == null) { + return "null"; + } else { + if (mCurrentValue instanceof List<?>) { + return "[" + JOINER.join((List<?>) mCurrentValue) + "]"; //$NON-NLS-1$ //$NON-NLS-2$ + } + return mCurrentValue.toString(); + } + } + + @Override + public String toString() { + return getType() + "=" + getStringValue(); //$NON-NLS-1$ + } + + @Override + public Object getValue() { + return mCurrentValue; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/GLState.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/GLState.java index c5bbd0b..dfc922a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/GLState.java +++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/GLState.java @@ -19,6 +19,8 @@ package com.android.ide.eclipse.gltrace.state; import com.android.ide.eclipse.gldebugger.GLEnum; import com.android.ide.eclipse.gltrace.state.GLIntegerProperty.DisplayRadix; +import java.util.Collections; + public class GLState { /** # of texture units modelled in the GL State. */ public static final int TEXTURE_UNIT_COUNT = 8; @@ -252,12 +254,37 @@ public class GLState { IGLProperty attachedShaders = new GLSparseArrayProperty(GLStateType.ATTACHED_SHADERS, attachedShaderId); + IGLProperty attributeName = new GLStringProperty(GLStateType.ATTRIBUTE_NAME, ""); + IGLProperty attributeType = new GLEnumProperty(GLStateType.ATTRIBUTE_TYPE, + GLEnum.GL_FLOAT_MAT4); + IGLProperty attributeSize = new GLIntegerProperty(GLStateType.ATTRIBUTE_SIZE, + Integer.valueOf(1)); + IGLProperty attributeValue = new GLObjectProperty(GLStateType.ATTRIBUTE_VALUE, + Collections.emptyList()); + IGLProperty perAttributeProperty = new GLCompositeProperty(GLStateType.PER_ATTRIBUTE_STATE, + attributeName, attributeType, attributeSize, attributeValue); + IGLProperty attributes = new GLSparseArrayProperty(GLStateType.ACTIVE_ATTRIBUTES, + perAttributeProperty); + + IGLProperty uniformName = new GLStringProperty(GLStateType.UNIFORM_NAME, ""); + IGLProperty uniformType = new GLEnumProperty(GLStateType.UNIFORM_TYPE, + GLEnum.GL_FLOAT_MAT4); + IGLProperty uniformSize = new GLIntegerProperty(GLStateType.UNIFORM_SIZE, + Integer.valueOf(1)); + IGLProperty uniformValue = new GLObjectProperty(GLStateType.UNIFORM_VALUE, + Collections.emptyList()); + IGLProperty perUniformProperty = new GLCompositeProperty(GLStateType.PER_UNIFORM_STATE, + uniformName, uniformType, uniformSize, uniformValue); + IGLProperty uniforms = new GLSparseArrayProperty(GLStateType.ACTIVE_UNIFORMS, + perUniformProperty); + IGLProperty perProgramState = new GLCompositeProperty(GLStateType.PER_PROGRAM_STATE, - attachedShaders); + attachedShaders, attributes, uniforms); IGLProperty programs = new GLSparseArrayProperty(GLStateType.PROGRAMS, perProgramState); - return new GLCompositeProperty(GLStateType.PROGRAM_STATE, currentProgram, programs); + return new GLCompositeProperty(GLStateType.PROGRAM_STATE, + currentProgram, programs); } private IGLProperty createShaderState() { diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/GLStateType.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/GLStateType.java index 4e0decb..42016fa 100644 --- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/GLStateType.java +++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/GLStateType.java @@ -120,6 +120,18 @@ public enum GLStateType { PER_PROGRAM_STATE("Per Program State"), ATTACHED_SHADERS("Attached Shaders"), ATTACHED_SHADER_ID("Attached Shader ID"), + ACTIVE_ATTRIBUTES("Attributes"), + PER_ATTRIBUTE_STATE("Per Attribute State"), + ATTRIBUTE_NAME("Name"), + ATTRIBUTE_TYPE("Type"), + ATTRIBUTE_SIZE("Size"), + ATTRIBUTE_VALUE("Value"), + ACTIVE_UNIFORMS("Uniforms"), + PER_UNIFORM_STATE("Per Uniform State"), + UNIFORM_NAME("Name"), + UNIFORM_TYPE("Type"), + UNIFORM_SIZE("Size"), + UNIFORM_VALUE("Value"), SHADERS("Shader Objects"), PER_SHADER_STATE("Per Shader State"), diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/CurrentProgramPropertyAccessor.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/CurrentProgramPropertyAccessor.java new file mode 100644 index 0000000..b977d87 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/CurrentProgramPropertyAccessor.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.gltrace.state.transforms; + +import com.android.ide.eclipse.gltrace.state.GLIntegerProperty; +import com.android.ide.eclipse.gltrace.state.GLStateType; +import com.android.ide.eclipse.gltrace.state.IGLProperty; + +public class CurrentProgramPropertyAccessor implements IGLPropertyAccessor { + private final int mContextId; + private final GLStateType mStateCategory; + private final int mLocation; + private final GLStateType mStateType; + private final IGLPropertyAccessor mCurrentProgramAccessor; + + public CurrentProgramPropertyAccessor(int contextid, GLStateType stateCategory, + int location, GLStateType stateType) { + mContextId = contextid; + mStateCategory = stateCategory; + mLocation = location; + mStateType = stateType; + + mCurrentProgramAccessor = GLPropertyAccessor.makeAccessor(contextid, + GLStateType.PROGRAM_STATE, + GLStateType.CURRENT_PROGRAM); + } + @Override + public IGLProperty getProperty(IGLProperty state) { + // obtain the current program + IGLProperty currentProgramProperty = mCurrentProgramAccessor.getProperty(state); + if (!(currentProgramProperty instanceof GLIntegerProperty)) { + return null; + } + + Integer program = (Integer) currentProgramProperty.getValue(); + + // now access the required program property + return GLPropertyAccessor.makeAccessor(mContextId, + GLStateType.PROGRAM_STATE, + GLStateType.PROGRAMS, + program, + mStateCategory, + Integer.valueOf(mLocation), + mStateType).getProperty(state); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/SparseArrayElementAddTransform.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/SparseArrayElementAddTransform.java index 1d6be8c..b7547bc 100644 --- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/SparseArrayElementAddTransform.java +++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/SparseArrayElementAddTransform.java @@ -21,11 +21,12 @@ import com.android.ide.eclipse.gltrace.state.IGLProperty; /** * A {@link SparseArrayElementAddTransform} changes given state by adding an - * element to a sparse array. + * element to a sparse array, if there is no item with the same key already. */ public class SparseArrayElementAddTransform implements IStateTransform { private IGLPropertyAccessor mAccessor; private int mKey; + private IGLProperty mOldValue; public SparseArrayElementAddTransform(IGLPropertyAccessor accessor, int key) { mAccessor = accessor; @@ -36,7 +37,11 @@ public class SparseArrayElementAddTransform implements IStateTransform { public void apply(IGLProperty currentState) { GLSparseArrayProperty propertyArray = getArray(currentState); if (propertyArray != null) { - propertyArray.add(mKey); + mOldValue = propertyArray.getProperty(mKey); + if (mOldValue == null) { + // add only if there is no item with this key already present + propertyArray.add(mKey); + } } } @@ -44,7 +49,10 @@ public class SparseArrayElementAddTransform implements IStateTransform { public void revert(IGLProperty currentState) { GLSparseArrayProperty propertyArray = getArray(currentState); if (propertyArray != null) { - propertyArray.delete(mKey); + if (mOldValue == null) { + // delete only if we actually added this key + propertyArray.delete(mKey); + } } } diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/StateTransformFactory.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/StateTransformFactory.java index d248bd7..3cc1004 100644 --- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/StateTransformFactory.java +++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/state/transforms/StateTransformFactory.java @@ -105,6 +105,34 @@ public class StateTransformFactory { return transformsForGlAttachShader(msg); case glDetachShader: return transformsForGlDetachShader(msg); + case glGetActiveAttrib: + return transformsForGlGetActiveAttrib(msg); + case glGetActiveUniform: + return transformsForGlGetActiveUniform(msg); + case glUniform1i: + case glUniform2i: + case glUniform3i: + case glUniform4i: + return transformsForGlUniform(msg, false); + case glUniform1f: + case glUniform2f: + case glUniform3f: + case glUniform4f: + return transformsForGlUniform(msg, true); + case glUniform1iv: + case glUniform2iv: + case glUniform3iv: + case glUniform4iv: + return transformsForGlUniformv(msg, false); + case glUniform1fv: + case glUniform2fv: + case glUniform3fv: + case glUniform4fv: + return transformsForGlUniformv(msg, true); + case glUniformMatrix2fv: + case glUniformMatrix3fv: + case glUniformMatrix4fv: + return transformsForGlUniformMatrix(msg); // Shader State Transformations case glCreateShader: @@ -863,6 +891,155 @@ public class StateTransformFactory { return Collections.singletonList(transform); } + private static List<IStateTransform> transformsForGlGetActiveAttribOrUniform( + GLMessage msg, boolean isAttrib) { + // void glGetActive[Attrib|Uniform](GLuint program, GLuint index, GLsizei bufsize, + // GLsizei* length, GLint* size, GLenum* type, GLchar* name); + int program = msg.getArgs(0).getIntValue(0); + int size = msg.getArgs(4).getIntValue(0); + GLEnum type = GLEnum.valueOf(msg.getArgs(5).getIntValue(0)); + String name = msg.getArgs(6).getCharValue(0).toStringUtf8(); + + // The 2nd argument (index) does not give the correct location of the + // attribute/uniform in device. The actual location is obtained from + // the getAttribLocation or getUniformLocation calls. The trace library + // appends this value as an additional last argument to this call. + int location = msg.getArgs(7).getIntValue(0); + + GLStateType activeInput; + GLStateType inputName; + GLStateType inputType; + GLStateType inputSize; + + if (isAttrib) { + activeInput = GLStateType.ACTIVE_ATTRIBUTES; + inputName = GLStateType.ATTRIBUTE_NAME; + inputType = GLStateType.ATTRIBUTE_TYPE; + inputSize = GLStateType.ATTRIBUTE_SIZE; + } else { + activeInput = GLStateType.ACTIVE_UNIFORMS; + inputName = GLStateType.UNIFORM_NAME; + inputType = GLStateType.UNIFORM_TYPE; + inputSize = GLStateType.UNIFORM_SIZE; + } + + IStateTransform addAttribute = new SparseArrayElementAddTransform( + GLPropertyAccessor.makeAccessor(msg.getContextId(), + GLStateType.PROGRAM_STATE, + GLStateType.PROGRAMS, + Integer.valueOf(program), + activeInput), + Integer.valueOf(location)); + IStateTransform setAttributeName = new PropertyChangeTransform( + GLPropertyAccessor.makeAccessor(msg.getContextId(), + GLStateType.PROGRAM_STATE, + GLStateType.PROGRAMS, + Integer.valueOf(program), + activeInput, + Integer.valueOf(location), + inputName), + name); + IStateTransform setAttributeType = new PropertyChangeTransform( + GLPropertyAccessor.makeAccessor(msg.getContextId(), + GLStateType.PROGRAM_STATE, + GLStateType.PROGRAMS, + Integer.valueOf(program), + activeInput, + Integer.valueOf(location), + inputType), + type); + IStateTransform setAttributeSize = new PropertyChangeTransform( + GLPropertyAccessor.makeAccessor(msg.getContextId(), + GLStateType.PROGRAM_STATE, + GLStateType.PROGRAMS, + Integer.valueOf(program), + activeInput, + Integer.valueOf(location), + inputSize), + Integer.valueOf(size)); + return Arrays.asList(addAttribute, setAttributeName, setAttributeType, setAttributeSize); + } + + private static List<IStateTransform> transformsForGlGetActiveAttrib(GLMessage msg) { + return transformsForGlGetActiveAttribOrUniform(msg, true); + } + + private static List<IStateTransform> transformsForGlGetActiveUniform(GLMessage msg) { + return transformsForGlGetActiveAttribOrUniform(msg, false); + } + + private static List<IStateTransform> transformsForGlUniformMatrix(GLMessage msg) { + // void glUniformMatrix[2|3|4]fv(GLint location, GLsizei count, GLboolean transpose, + // const GLfloat *value); + int location = msg.getArgs(0).getIntValue(0); + List<Float> uniforms = msg.getArgs(3).getFloatValueList(); + + IStateTransform setValues = new PropertyChangeTransform( + new CurrentProgramPropertyAccessor(msg.getContextId(), + GLStateType.ACTIVE_UNIFORMS, + location, + GLStateType.UNIFORM_VALUE), + uniforms); + + return Collections.singletonList(setValues); + } + + private static List<IStateTransform> transformsForGlUniformv(GLMessage msg, boolean isFloats) { + // void glUniform1fv(GLint location, GLsizei count, const GLfloat *value); + int location = msg.getArgs(0).getIntValue(0); + List<?> uniforms; + if (isFloats) { + uniforms = msg.getArgs(2).getFloatValueList(); + } else { + uniforms = msg.getArgs(2).getIntValueList(); + } + + IStateTransform setValues = new PropertyChangeTransform( + new CurrentProgramPropertyAccessor(msg.getContextId(), + GLStateType.ACTIVE_UNIFORMS, + location, + GLStateType.UNIFORM_VALUE), + uniforms); + + return Collections.singletonList(setValues); + } + + private static List<IStateTransform> transformsForGlUniform(GLMessage msg, boolean isFloats) { + // void glUniform1f(GLint location, GLfloat v0); + // void glUniform2f(GLint location, GLfloat v0, GLfloat v1); + // .. 3f + // .. 4f + // void glUniform1i(GLint location, GLfloat v0); + // void glUniform2i(GLint location, GLfloat v0, GLfloat v1); + // .. 3i + // .. 4i + + int location = msg.getArgs(0).getIntValue(0); + List<?> uniforms; + if (isFloats) { + List<Float> args = new ArrayList<Float>(msg.getArgsCount() - 1); + for (int i = 1; i < msg.getArgsCount(); i++) { + args.add(Float.valueOf(msg.getArgs(1).getFloatValue(0))); + } + uniforms = args; + } else { + List<Integer> args = new ArrayList<Integer>(msg.getArgsCount() - 1); + for (int i = 1; i < msg.getArgsCount(); i++) { + args.add(Integer.valueOf(msg.getArgs(1).getIntValue(0))); + } + uniforms = args; + } + + IStateTransform setValues = new PropertyChangeTransform( + new CurrentProgramPropertyAccessor(msg.getContextId(), + GLStateType.ACTIVE_UNIFORMS, + location, + GLStateType.UNIFORM_VALUE), + uniforms); + + return Collections.singletonList(setValues); + } + private static List<IStateTransform> transformsForGlCreateShader(GLMessage msg) { // GLuint glCreateShader(GLenum shaderType); GLEnum shaderType = GLEnum.valueOf(msg.getArgs(0).getIntValue(0)); diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/DetailsPage.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/DetailsPage.java new file mode 100644 index 0000000..1fb64b4 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/DetailsPage.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.gltrace.views; + +import com.android.ide.eclipse.gltrace.editors.GLCallGroups.GLCallNode; +import com.android.ide.eclipse.gltrace.editors.GLFunctionTraceViewer; +import com.android.ide.eclipse.gltrace.model.GLCall; +import com.android.ide.eclipse.gltrace.model.GLTrace; +import com.android.ide.eclipse.gltrace.state.IGLProperty; + +import org.eclipse.jface.action.IContributionItem; +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.TreeSelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.StackLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.ISelectionListener; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.part.IPageSite; +import org.eclipse.ui.part.Page; + +import java.util.Arrays; +import java.util.List; + +public class DetailsPage extends Page implements ISelectionListener { + private final GLTrace mTrace; + + private IToolBarManager mToolBarManager; + private Composite mTopComposite; + private StackLayout mStackLayout; + private Composite mBlankComposite; + + private List<IStateDetailsProvider> mStateDetailProviders = Arrays.asList( + new ShaderSourceDetailsProvider(), + new TextureImageDetailsProvider()); + + public DetailsPage(GLTrace trace) { + mTrace = trace; + } + + @Override + public void createControl(Composite parent) { + mTopComposite = new Composite(parent, SWT.NONE); + mStackLayout = new StackLayout(); + mTopComposite.setLayout(mStackLayout); + mTopComposite.setLayoutData(new GridData(GridData.FILL_BOTH)); + + mBlankComposite = new Composite(mTopComposite, SWT.NONE); + + mToolBarManager = getSite().getActionBars().getToolBarManager(); + + for (IStateDetailsProvider provider : mStateDetailProviders) { + provider.createControl(mTopComposite); + + for (IContributionItem item: provider.getToolBarItems()) { + mToolBarManager.add(item); + } + } + + setDetailsProvider(null); + } + + private void setDetailsProvider(IStateDetailsProvider provider) { + for (IContributionItem item: mToolBarManager.getItems()) { + item.setVisible(false); + } + + if (provider == null) { + setTopControl(mBlankComposite); + } else { + setTopControl(provider.getControl()); + + for (IContributionItem item: provider.getToolBarItems()) { + item.setVisible(true); + } + } + + mToolBarManager.update(true); + } + + private void setTopControl(Control c) { + mStackLayout.topControl = c; + mTopComposite.layout(); + } + + @Override + public Control getControl() { + return mTopComposite; + } + + @Override + public void init(IPageSite pageSite) { + super.init(pageSite); + pageSite.getPage().addSelectionListener(this); + } + + @Override + public void dispose() { + getSite().getPage().removeSelectionListener(this); + + for (IStateDetailsProvider provider : mStateDetailProviders) { + provider.disposeControl(); + } + + super.dispose(); + } + + @Override + public void setFocus() { + } + + @Override + public void selectionChanged(IWorkbenchPart part, ISelection selection) { + if (part instanceof GLFunctionTraceViewer) { + GLCall selectedCall = getSelectedCall((GLFunctionTraceViewer) part, selection); + if (selectedCall == null) { + return; + } + + callSelected(selectedCall); + return; + } else if (part instanceof StateView) { + IGLProperty selectedProperty = getSelectedProperty((StateView) part, selection); + if (selectedProperty == null) { + return; + } + + stateVariableSelected(selectedProperty); + return; + } + + return; + } + + private void stateVariableSelected(IGLProperty property) { + for (IStateDetailsProvider p : mStateDetailProviders) { + if (p.isApplicable(property)) { + p.updateControl(property); + setDetailsProvider(p); + return; + } + } + + setDetailsProvider(null); + return; + } + + private void callSelected(GLCall selectedCall) { + } + + private GLCall getSelectedCall(GLFunctionTraceViewer part, ISelection selection) { + if (part.getTrace() != mTrace) { + return null; + } + + if (!(selection instanceof TreeSelection)) { + return null; + } + + Object data = ((TreeSelection) selection).getFirstElement(); + if (data instanceof GLCallNode) { + return ((GLCallNode) data).getCall(); + } else { + return null; + } + } + + private IGLProperty getSelectedProperty(StateView view, ISelection selection) { + if (!(selection instanceof IStructuredSelection)) { + return null; + } + + IStructuredSelection ssel = (IStructuredSelection) selection; + @SuppressWarnings("rawtypes") + List objects = ssel.toList(); + if (objects.size() > 0) { + Object data = objects.get(0); + if (data instanceof IGLProperty) { + return (IGLProperty) data; + } + } + + return null; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/DetailsView.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/DetailsView.java new file mode 100644 index 0000000..015cf61 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/DetailsView.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.gltrace.views; + +import com.android.ide.eclipse.gltrace.editors.GLFunctionTraceViewer; + +import org.eclipse.ui.IWorkbenchPart; + +public class DetailsView extends GLPageBookView { + public static final String ID = "com.android.ide.eclipse.gltrace.views.Details"; //$NON-NLS-1$ + + public DetailsView() { + super(""); //$NON-NLS-1$ + } + + @Override + protected PageRec doCreatePage(IWorkbenchPart part) { + if (!(part instanceof GLFunctionTraceViewer)) { + return null; + } + + GLFunctionTraceViewer viewer = (GLFunctionTraceViewer) part; + DetailsPage page = viewer.getDetailsPage(); + + initPage(page); + page.createControl(getPageBook()); + + return new PageRec(part, page); + } + + @Override + protected void doDestroyPage(IWorkbenchPart part, PageRec pageRecord) { + DetailsPage page = (DetailsPage) pageRecord.page; + page.dispose(); + pageRecord.dispose(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/FitToCanvasAction.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/FitToCanvasAction.java new file mode 100644 index 0000000..ccd2cbe --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/FitToCanvasAction.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.gltrace.views; + +import com.android.ide.eclipse.gldebugger.Activator; +import com.android.ide.eclipse.gltrace.widgets.ImageCanvas; + +import org.eclipse.jface.action.Action; + +public class FitToCanvasAction extends Action { + private ImageCanvas mImageCanvas; + + public FitToCanvasAction(boolean fitByDefault, ImageCanvas canvas) { + super("Fit to Canvas", Activator.getImageDescriptor("/icons/zoomfit.png")); //$NON-NLS-2$ + setToolTipText("Fit Image to Canvas"); + mImageCanvas = canvas; + + setChecked(fitByDefault); + mImageCanvas.setFitToCanvas(fitByDefault); + } + + @Override + public void run() { + mImageCanvas.setFitToCanvas(isChecked()); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/FrameBufferView.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/FrameBufferView.java index 308134b..50eafd2 100644 --- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/FrameBufferView.java +++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/FrameBufferView.java @@ -25,7 +25,7 @@ import org.eclipse.ui.IWorkbenchPart; * currently displayed frame. */ public class FrameBufferView extends GLPageBookView { - public static final String ID = "com.android.ide.eclipse.gltrace.views.FrameBufferView"; //$NON-NLS-1$ + public static final String ID = "com.android.ide.eclipse.gltrace.views.FrameBuffer"; //$NON-NLS-1$ public FrameBufferView() { super("Open a GL Trace file to view the framebuffer contents."); diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/FrameBufferViewPage.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/FrameBufferViewPage.java index 67ec762..09b43b2 100644 --- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/FrameBufferViewPage.java +++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/FrameBufferViewPage.java @@ -16,14 +16,12 @@ package com.android.ide.eclipse.gltrace.views; -import com.android.ide.eclipse.gldebugger.Activator; import com.android.ide.eclipse.gltrace.editors.GLFunctionTraceViewer; import com.android.ide.eclipse.gltrace.editors.GLCallGroups.GLCallNode; import com.android.ide.eclipse.gltrace.model.GLCall; import com.android.ide.eclipse.gltrace.model.GLTrace; import com.android.ide.eclipse.gltrace.widgets.ImageCanvas; -import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.TreeSelection; @@ -31,7 +29,6 @@ import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; -import org.eclipse.ui.IActionBars; import org.eclipse.ui.ISelectionListener; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.part.IPageSite; @@ -50,9 +47,7 @@ public class FrameBufferViewPage extends Page implements ISelectionListener { public void createControl(Composite parent) { mImageCanvas = new ImageCanvas(parent); - IActionBars actionBars = getSite().getActionBars(); - IToolBarManager toolbarManager = actionBars.getToolBarManager(); - + IToolBarManager toolbarManager = getSite().getActionBars().getToolBarManager(); toolbarManager.add(new FitToCanvasAction(true, mImageCanvas)); } @@ -99,7 +94,6 @@ public class FrameBufferViewPage extends Page implements ISelectionListener { } if (selectedCall == null) { - System.out.println("null selection"); return; } @@ -122,22 +116,4 @@ public class FrameBufferViewPage extends Page implements ISelectionListener { setImage(mTrace.getImage(call)); } } - - private static class FitToCanvasAction extends Action { - private ImageCanvas mImageCanvas; - - public FitToCanvasAction(boolean fit, ImageCanvas canvas) { - super("Fit to Canvas", Activator.getImageDescriptor("/icons/zoomfit.png")); //$NON-NLS-2$ - setToolTipText("Fit Image to Canvas"); - mImageCanvas = canvas; - - setChecked(fit); - mImageCanvas.setFitToCanvas(fit); - } - - @Override - public void run() { - mImageCanvas.setFitToCanvas(isChecked()); - } - } } diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/IStateDetailsProvider.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/IStateDetailsProvider.java new file mode 100644 index 0000000..7fe7c56 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/IStateDetailsProvider.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.gltrace.views; + +import com.android.ide.eclipse.gltrace.state.IGLProperty; + +import org.eclipse.jface.action.IContributionItem; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +import java.util.List; + +public interface IStateDetailsProvider { + /** Is this provider applicable for given GL state property? */ + boolean isApplicable(IGLProperty state); + + /** Create the controls to display the details. */ + void createControl(Composite parent); + + /** Dispose off any created controls. */ + void disposeControl(); + + /** Obtain the top level control used by this detail provider. */ + Control getControl(); + + /** Update the detail view for given GL state property. */ + void updateControl(IGLProperty state); + + /** Obtain a list of tool bar items to be displayed when this provider is active. */ + List<IContributionItem> getToolBarItems(); +} diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/ImageViewPart.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/ImageViewPart.java deleted file mode 100644 index 67b879a..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/ImageViewPart.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.gltrace.views; - -import com.android.ide.eclipse.gldebugger.Activator; -import com.android.ide.eclipse.gltrace.widgets.ImageCanvas; - -import org.eclipse.jface.action.Action; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.ui.ISelectionListener; -import org.eclipse.ui.ISelectionService; -import org.eclipse.ui.IWorkbenchPart; -import org.eclipse.ui.part.ViewPart; - -/** - * The {@link ImageViewPart} is an abstract implementation of an Eclipse View - * that holds an image canvas. This class registers itself to listen to the workbench - * selection service. Subclasses must implement the - * {@link #selectionChanged(IWorkbenchPart, ISelection)} method, and call {@link #setImage(Image)} - * to update the image to be displayed. - */ -public abstract class ImageViewPart extends ViewPart implements ISelectionListener { - private boolean mFitToCanvasDefault = true; - private ImageCanvas mImageCanvas; - - public ImageViewPart(boolean fitToCanvasByDefault) { - mFitToCanvasDefault = fitToCanvasByDefault; - } - - @Override - public void createPartControl(Composite parent) { - createImageCanvas(parent); - createToolbar(); - - ISelectionService selectionService = getSite().getWorkbenchWindow().getSelectionService(); - selectionService.addPostSelectionListener(this); - } - - @Override - public void dispose() { - if (mImageCanvas != null) { - mImageCanvas.dispose(); - mImageCanvas = null; - } - - ISelectionService selectionService = getSite().getWorkbenchWindow().getSelectionService(); - selectionService.removePostSelectionListener(this); - super.dispose(); - } - - @Override - public void setFocus() { - if (mImageCanvas != null) { - mImageCanvas.setFocus(); - } - } - - private class FitToCanvasAction extends Action { - public FitToCanvasAction() { - super("Fit to Canvas", Activator.getImageDescriptor("/icons/zoomfit.png")); //$NON-NLS-2$ - setToolTipText("Fit Image to Canvas"); - setChecked(mFitToCanvasDefault); - } - - @Override - public void run() { - mImageCanvas.setFitToCanvas(isChecked()); - } - } - - private void createImageCanvas(Composite parent) { - mImageCanvas = new ImageCanvas(parent); - mImageCanvas.setFitToCanvas(mFitToCanvasDefault); - } - - private void createToolbar() { - getViewSite().getActionBars().getToolBarManager().add(new FitToCanvasAction()); - } - - protected void setImage(final Image image) { - Display.getDefault().asyncExec(new Runnable() { - @Override - public void run() { - mImageCanvas.setImage(image); - } - }); - }; - - @Override - public abstract void selectionChanged(IWorkbenchPart part, ISelection selection); -} diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/ShaderSourceDetailsProvider.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/ShaderSourceDetailsProvider.java new file mode 100644 index 0000000..bad3042 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/ShaderSourceDetailsProvider.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.gltrace.views; + +import com.android.ide.eclipse.gltrace.state.GLCompositeProperty; +import com.android.ide.eclipse.gltrace.state.GLStateType; +import com.android.ide.eclipse.gltrace.state.GLStringProperty; +import com.android.ide.eclipse.gltrace.state.IGLProperty; + +import org.eclipse.jface.action.IContributionItem; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Text; + +import java.util.Collections; +import java.util.List; + +public class ShaderSourceDetailsProvider implements IStateDetailsProvider { + private Text mTextControl; + + @Override + public boolean isApplicable(IGLProperty state) { + return getShaderSourceProperty(state) != null; + } + + @Override + public void createControl(Composite parent) { + mTextControl = new Text(parent, SWT.BORDER | SWT.READ_ONLY | SWT.MULTI | SWT.WRAP); + mTextControl.setEditable(false); + } + + @Override + public void disposeControl() { + } + + @Override + public Control getControl() { + return mTextControl; + } + + @Override + public void updateControl(IGLProperty state) { + IGLProperty shaderSrcProperty = getShaderSourceProperty(state); + if (shaderSrcProperty instanceof GLStringProperty) { + String shaderSrc = ((GLStringProperty) shaderSrcProperty).getStringValue(); + mTextControl.setText(shaderSrc); + mTextControl.setEnabled(true); + } else { + mTextControl.setText(""); //$NON-NLS-1$ + mTextControl.setEnabled(false); + } + } + + /** + * Get the {@link GLStateType#SHADER_SOURCE} property given a node in + * the state hierarchy. + * @param state any node in the GL state hierarchy + * @return The {@link GLStateType#SHADER_SOURCE} property if a unique instance + * of it can be accessed from the given node, null otherwise. + * A unique instance can be accessed if the given node is + * either the requested node itself, or its parent or sibling. + */ + private IGLProperty getShaderSourceProperty(IGLProperty state) { + if (state.getType() == GLStateType.SHADER_SOURCE) { + // given node is the requested node + return state; + } + + if (state.getType() != GLStateType.PER_SHADER_STATE) { + // if it is not the parent, then it could be a sibling, in which case + // we go up a level to its parent + state = state.getParent(); + } + + if (state != null && state.getType() == GLStateType.PER_SHADER_STATE) { + // if it is the parent, we can access the required property + return ((GLCompositeProperty) state).getProperty(GLStateType.SHADER_SOURCE); + } + + return null; + } + + @Override + public List<IContributionItem> getToolBarItems() { + return Collections.emptyList(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/TextureImageDetailsProvider.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/TextureImageDetailsProvider.java new file mode 100644 index 0000000..2b0034b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/TextureImageDetailsProvider.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.gltrace.views; + +import com.android.ide.eclipse.gltrace.state.GLCompositeProperty; +import com.android.ide.eclipse.gltrace.state.GLStateType; +import com.android.ide.eclipse.gltrace.state.GLStringProperty; +import com.android.ide.eclipse.gltrace.state.IGLProperty; +import com.android.ide.eclipse.gltrace.widgets.ImageCanvas; + +import org.eclipse.jface.action.ActionContributionItem; +import org.eclipse.jface.action.IContributionItem; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; + +import java.util.Collections; +import java.util.List; + +public class TextureImageDetailsProvider implements IStateDetailsProvider { + private ImageCanvas mImageCanvas; + private FitToCanvasAction mFitToCanvasAction; + private List<IContributionItem> mToolBarItems; + + @Override + public boolean isApplicable(IGLProperty state) { + return getTextureImageProperty(state) != null; + } + + @Override + public void createControl(Composite parent) { + mImageCanvas = new ImageCanvas(parent); + mImageCanvas.setFitToCanvas(false); + + mFitToCanvasAction = new FitToCanvasAction(false, mImageCanvas); + mToolBarItems = Collections.singletonList( + (IContributionItem) new ActionContributionItem(mFitToCanvasAction)); + } + + @Override + public void disposeControl() { + mImageCanvas.dispose(); + mImageCanvas = null; + } + + @Override + public Control getControl() { + return mImageCanvas; + } + + @Override + public void updateControl(IGLProperty state) { + IGLProperty imageProperty = getTextureImageProperty(state); + if (imageProperty == null) { + return; + } + + String texturePath = ((GLStringProperty) imageProperty).getStringValue(); + if (texturePath != null) { + mImageCanvas.setImage(new Image(Display.getDefault(), texturePath)); + mImageCanvas.setFitToCanvas(false); + return; + } + } + + /** + * Get the {@link GLStateType#TEXTURE_IMAGE} property given a node in + * the state hierarchy. + * @param state any node in the GL state hierarchy + * @return The {@link GLStateType#TEXTURE_IMAGE} property if a unique instance + * of it can be accessed from the given node, null otherwise. + * A unique instance can be accessed if the given node is + * either the requested node itself, or its parent or sibling. + */ + private IGLProperty getTextureImageProperty(IGLProperty state) { + if (state.getType() == GLStateType.TEXTURE_IMAGE) { + // given node is the requested node + return state; + } + + if (state.getType() != GLStateType.PER_TEXTURE_STATE) { + // if it is not the parent, then it could be a sibling, in which case + // we go up a level to its parent + state = state.getParent(); + } + + if (state != null && state.getType() == GLStateType.PER_TEXTURE_STATE) { + // if it is the parent, we can access the required property + return ((GLCompositeProperty) state).getProperty(GLStateType.TEXTURE_IMAGE); + } + + return null; + } + + @Override + public List<IContributionItem> getToolBarItems() { + return mToolBarItems; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/TextureView.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/TextureView.java deleted file mode 100644 index a355bae..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/views/TextureView.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.gltrace.views; - -import com.android.ide.eclipse.gltrace.state.GLCompositeProperty; -import com.android.ide.eclipse.gltrace.state.GLStateType; -import com.android.ide.eclipse.gltrace.state.GLStringProperty; -import com.android.ide.eclipse.gltrace.state.IGLProperty; - -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.widgets.Display; -import org.eclipse.ui.IWorkbenchPart; - -import java.util.List; - -public class TextureView extends ImageViewPart { - public TextureView() { - super(false); - } - - public static final String ID = "com.android.ide.eclipse.gltrace.views.Texture"; //$NON-NLS-1$ - - @Override - public void selectionChanged(IWorkbenchPart part, ISelection selection) { - if (!(part instanceof StateView)) { - return; - } - - if (!(selection instanceof IStructuredSelection)) { - return; - } - - IStructuredSelection ssel = (IStructuredSelection) selection; - @SuppressWarnings("rawtypes") - List objects = ssel.toList(); - if (objects.size() > 0) { - Object data = objects.get(0); - if (!(data instanceof IGLProperty)) { - return; - } - - String textureImagePath = getTextureImage((IGLProperty) data); - if (textureImagePath == null) { - setImage(null); - return; - } - - setImage(getImage(textureImagePath)); - } - } - - private Image getImage(String imagePath) { - return new Image(Display.getDefault(), imagePath); - } - - /** - * Extract the TEXTURE_IMAGE property from the gl state hierarchy. - * The TEXTURE_IMAGE property fits in the hierarchy like so: - * ... - * |---PER_TEXTURE_STATE - * |-- - * |-- TEXTURE_IMAGE - * - * So we can extract the TEXTURE_IMAGE if the given property is either PER_TEXTURE_STATE, - * or a child of PER_TEXTURE_STATE. - */ - private String getTextureImage(IGLProperty property) { - // if the selected property is the texture image, then we just return its value - if (property.getType() == GLStateType.TEXTURE_IMAGE) { - return ((GLStringProperty) property).getStringValue(); - } - - if (property.getType() != GLStateType.PER_TEXTURE_STATE) { - // See if the property is a child of PER_TEXTURE_STATE - IGLProperty parent = property.getParent(); - if (parent == null || parent.getType() != GLStateType.PER_TEXTURE_STATE) { - return null; - } else { - property = parent; - } - } - - // property is now the parent property of TEXTURE_IMAGE - GLCompositeProperty perTextureState = (GLCompositeProperty) property; - IGLProperty imageProperty = perTextureState.getProperty(GLStateType.TEXTURE_IMAGE); - return ((GLStringProperty) imageProperty).getStringValue(); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/lint/ProjectLintConfigurationTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/lint/ProjectLintConfigurationTest.java index 09eb181..96014de 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/lint/ProjectLintConfigurationTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/lint/ProjectLintConfigurationTest.java @@ -213,7 +213,7 @@ public class ProjectLintConfigurationTest extends AdtProjectTest { } @Override - public void log(Throwable exception, String format, Object... args) { + public void log(Severity severity, Throwable exception, String format, Object... args) { } @Override diff --git a/lint/cli/src/com/android/tools/lint/HtmlReporter.java b/lint/cli/src/com/android/tools/lint/HtmlReporter.java index 45aac72..9cc9c6f 100644 --- a/lint/cli/src/com/android/tools/lint/HtmlReporter.java +++ b/lint/cli/src/com/android/tools/lint/HtmlReporter.java @@ -334,7 +334,7 @@ class HtmlReporter extends Reporter { mWriter.write("</div>\n"); //$NON-NLS-1$ mWriter.write("Severity: "); - if (severity == Severity.ERROR) { + if (severity == Severity.ERROR || severity == Severity.FATAL) { mWriter.write("<span class=\"error\">"); //$NON-NLS-1$ } else if (severity == Severity.WARNING) { mWriter.write("<span class=\"warning\">"); //$NON-NLS-1$ @@ -507,7 +507,7 @@ class HtmlReporter extends Reporter { boolean isError = false; for (Warning warning : warnings) { - if (warning.severity == Severity.ERROR) { + if (warning.severity == Severity.ERROR || warning.severity == Severity.FATAL) { isError = true; break; } diff --git a/lint/cli/src/com/android/tools/lint/LintCliXmlParser.java b/lint/cli/src/com/android/tools/lint/LintCliXmlParser.java index 9ee5ee8..e777e99 100644 --- a/lint/cli/src/com/android/tools/lint/LintCliXmlParser.java +++ b/lint/cli/src/com/android/tools/lint/LintCliXmlParser.java @@ -16,6 +16,7 @@ package com.android.tools.lint; +import com.android.annotations.Nullable; import com.android.tools.lint.client.api.IDomParser; import com.android.tools.lint.client.api.IssueRegistry; import com.android.tools.lint.detector.api.Location; @@ -161,6 +162,7 @@ public class LintCliXmlParser extends PositionXmlParser implements IDomParser { private class LocationHandle implements Handle { private File mFile; private Node mNode; + private Object mClientData; public LocationHandle(File file, Node node) { mFile = file; @@ -176,5 +178,16 @@ public class LintCliXmlParser extends PositionXmlParser implements IDomParser { return null; } + + @Override + public void setClientData(@Nullable Object clientData) { + mClientData = clientData; + } + + @Override + @Nullable + public Object getClientData() { + return mClientData; + } } } diff --git a/lint/cli/src/com/android/tools/lint/LombokParser.java b/lint/cli/src/com/android/tools/lint/LombokParser.java index e1a3c3a..15b1073 100644 --- a/lint/cli/src/com/android/tools/lint/LombokParser.java +++ b/lint/cli/src/com/android/tools/lint/LombokParser.java @@ -16,6 +16,7 @@ package com.android.tools.lint; +import com.android.annotations.Nullable; import com.android.tools.lint.client.api.IJavaParser; import com.android.tools.lint.detector.api.JavaContext; import com.android.tools.lint.detector.api.Location; @@ -113,6 +114,7 @@ public class LombokParser implements IJavaParser { private class LocationHandle implements Handle { private File mFile; private Node mNode; + private Object mClientData; public LocationHandle(File file, Node node) { mFile = file; @@ -124,5 +126,17 @@ public class LombokParser implements IJavaParser { Position pos = mNode.getPosition(); return Location.create(mFile, null /*contents*/, pos.getStart(), pos.getEnd()); } + + + @Override + public void setClientData(@Nullable Object clientData) { + mClientData = clientData; + } + + @Override + @Nullable + public Object getClientData() { + return mClientData; + } } } diff --git a/lint/cli/src/com/android/tools/lint/Main.java b/lint/cli/src/com/android/tools/lint/Main.java index d319648..1484a1d 100644 --- a/lint/cli/src/com/android/tools/lint/Main.java +++ b/lint/cli/src/com/android/tools/lint/Main.java @@ -264,6 +264,9 @@ public class Main extends LintClient { System.exit(ERRNO_INVALIDARGS); } File output = new File(args[++index]); + // Get an absolute path such that we can ask its parent directory for + // write permission etc. + output = output.getAbsoluteFile(); if (output.isDirectory() || (!output.exists() && output.getName().indexOf('.') == -1)) { if (!output.exists()) { @@ -293,7 +296,7 @@ public class Main extends LintClient { System.exit(ERRNO_EXISTS); } } - if (!output.getParentFile().canWrite()) { + if (output.getParentFile() != null && !output.getParentFile().canWrite()) { System.err.println("Cannot write HTML output file " + output); System.exit(ERRNO_EXISTS); } @@ -797,7 +800,7 @@ public class Main extends LintClient { } @Override - public void log(Throwable exception, String format, Object... args) { + public void log(Severity severity, Throwable exception, String format, Object... args) { System.out.flush(); if (!mQuiet) { // Place the error message on a line of its own since we're printing '.' etc @@ -853,8 +856,13 @@ public class Main extends LintClient { if (severity == Severity.IGNORE) { return; } - if (severity == Severity.ERROR){ + if (severity == Severity.FATAL) { mFatal = true; + // From here on, treat the fatal error as an error such that we don't display + // both "Fatal:" and "Error:" etc in the error output. + severity = Severity.ERROR; + } + if (severity == Severity.ERROR) { mErrorCount++; } else { mWarningCount++; diff --git a/lint/cli/src/com/android/tools/lint/MultiProjectHtmlReporter.java b/lint/cli/src/com/android/tools/lint/MultiProjectHtmlReporter.java index 6dad258..db7b4fb 100644 --- a/lint/cli/src/com/android/tools/lint/MultiProjectHtmlReporter.java +++ b/lint/cli/src/com/android/tools/lint/MultiProjectHtmlReporter.java @@ -103,7 +103,7 @@ class MultiProjectHtmlReporter extends HtmlReporter { int projectErrorCount = 0; int projectWarningCount = 0; for (Warning warning: issues) { - if (warning.severity == Severity.ERROR) { + if (warning.severity == Severity.ERROR || warning.severity == Severity.FATAL) { projectErrorCount++; } else if (warning.severity == Severity.WARNING) { projectWarningCount++; diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java index fb62538..48e86b5 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java @@ -23,6 +23,7 @@ import com.android.tools.lint.detector.api.Detector; import com.android.tools.lint.detector.api.Issue; import com.android.tools.lint.detector.api.Location; import com.android.tools.lint.detector.api.Project; +import com.android.tools.lint.detector.api.Severity; import com.google.common.annotations.Beta; import org.w3c.dom.Document; @@ -92,14 +93,31 @@ public abstract class LintClient { @Nullable Object data); /** - * Send an exception to the log + * Send an exception or error message (with warning severity) to the log * * @param exception the exception, possibly null * @param format the error message using {@link String#format} syntax, possibly null * (though in that case the exception should not be null) * @param args any arguments for the format string */ + public void log( + @Nullable Throwable exception, + @Nullable String format, + @Nullable Object... args) { + log(Severity.WARNING, exception, format, args); + } + + /** + * Send an exception or error message to the log + * + * @param severity the severity of the warning + * @param exception the exception, possibly null + * @param format the error message using {@link String#format} syntax, possibly null + * (though in that case the exception should not be null) + * @param args any arguments for the format string + */ public abstract void log( + @NonNull Severity severity, @Nullable Throwable exception, @Nullable String format, @Nullable Object... args); @@ -159,6 +177,7 @@ public abstract class LintClient { /** * Returns the list of output folders for class files + * * @param project the project to look up class file locations for * @return a list of output folders to search for .class files */ @@ -168,6 +187,17 @@ public abstract class LintClient { } /** + * Returns the list of Java libraries + * + * @param project the project to look up jar dependencies for + * @return a list of jar dependencies containing .class files + */ + @NonNull + public List<File> getJavaLibraries(@NonNull Project project) { + return getEclipseClasspath(project, "lib"); //$NON-NLS-1$ + } + + /** * Returns the {@link SdkInfo} to use for the given project. * * @param project the project to look up an {@link SdkInfo} for diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java index c02d735..d43d02e 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java @@ -26,6 +26,7 @@ import static com.android.tools.lint.detector.api.LintConstants.RES_FOLDER; import static com.android.tools.lint.detector.api.LintConstants.SUPPRESS_ALL; import static com.android.tools.lint.detector.api.LintConstants.SUPPRESS_LINT; import static com.android.tools.lint.detector.api.LintConstants.TOOLS_URI; +import static org.objectweb.asm.Opcodes.ASM4; import com.android.annotations.NonNull; import com.android.annotations.Nullable; @@ -51,6 +52,7 @@ import com.google.common.io.ByteStreams; import com.google.common.io.Files; import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldNode; @@ -731,13 +733,7 @@ public class LintDriver { return; } - if (mScope.contains(Scope.CLASS_FILE)) { - List<Detector> detectors = mScopeDetectors.get(Scope.CLASS_FILE); - if (detectors != null && detectors.size() > 0) { - List<File> binFolders = project.getJavaClassFolders(); - checkClasses(project, main, binFolders, detectors); - } - } + checkClasses(project, main); if (mCanceled) { return; @@ -762,6 +758,32 @@ public class LintDriver { } } + /** + * Map from VM class name to corresponding super class VM name, if available. + * This map is typically null except <b>during</b> class processing. + */ + private Map<String, String> mSuperClassMap; + + /** + * Returns the super class for the given class name, + * which should be in VM format (e.g. java/lang/Integer, not java.lang.Integer). + * If the super class is not known, returns null. This can happen if + * the given class is not a known class according to the project or its + * libraries, for example because it refers to one of the core libraries which + * are not analyzed by lint. + * + * @param name the fully qualified class name + * @return the corresponding super class name (in VM format), or null if not known + */ + @Nullable + public String getSuperClass(@NonNull String name) { + if (mSuperClassMap == null) { + throw new IllegalStateException("Only callable during ClassScanner#checkClass"); + } + assert name.indexOf('.') == -1 : "Use VM signatures, e.g. java/lang/Integer"; + return mSuperClassMap.get(name); + } + @Nullable private static List<Detector> union( @Nullable List<Detector> list1, @@ -786,17 +808,120 @@ public class LintDriver { } } - private void checkClasses( - @NonNull Project project, - @Nullable Project main, - @NonNull List<File> binFolders, - @NonNull List<Detector> checks) { - if (binFolders.size() == 0) { + /** Check the classes in this project (and if applicable, in any library projects */ + private void checkClasses(Project project, Project main) { + if (!(mScope.contains(Scope.CLASS_FILE) || mScope.contains(Scope.JAVA_LIBRARIES))) { + return; + } + + // We need to read in all the classes up front such that we can initialize + // the parent chains (such that for example for a virtual dispatch, we can + // also check the super classes). + + List<File> libraries = project.getJavaLibraries(); + List<ClassEntry> libraryEntries; + if (libraries.size() > 0) { + libraryEntries = new ArrayList<ClassEntry>(64); + findClasses(libraryEntries, libraries); + } else { + libraryEntries = Collections.emptyList(); + } + + List<File> classFolders = project.getJavaClassFolders(); + List<ClassEntry> classEntries; + if (classFolders.size() == 0) { //mClient.log(null, "Warning: Class-file checks are enabled, but no " + // "output folders found. Does the project need to be built first?"); + classEntries = Collections.emptyList(); + } else { + classEntries = new ArrayList<ClassEntry>(64); + findClasses(classEntries, classFolders); + } + + if (getPhase() == 1) { + mSuperClassMap = getSuperMap(libraryEntries, classEntries); } - for (File classPathEntry : binFolders) { + // Actually run the detectors. Libraries should be called before the + // main classes. + runClassDetectors(Scope.JAVA_LIBRARIES, libraryEntries, project, main); + + if (mCanceled) { + return; + } + + runClassDetectors(Scope.CLASS_FILE, classEntries, project, main); + } + + private void runClassDetectors(Scope scope, List<ClassEntry> entries, + Project project, Project main) { + if (mScope.contains(scope)) { + List<Detector> classDetectors = mScopeDetectors.get(scope); + if (classDetectors != null && classDetectors.size() > 0 && entries.size() > 0) { + for (ClassEntry entry : entries) { + ClassReader reader = new ClassReader(entry.bytes); + ClassNode classNode = new ClassNode(); + reader.accept(classNode, 0 /* flags */); + if (isSuppressed(null, classNode)) { + // Class was annotated with suppress all -- no need to look any further + continue; + } + + ClassContext context = new ClassContext(this, project, main, + entry.file, entry.jarFile, entry.binDir, entry.bytes, + classNode, scope == Scope.JAVA_LIBRARIES /*fromLibrary*/); + runClassDetectors(context, classDetectors); + + if (mCanceled) { + return; + } + } + } + } + } + + private Map<String, String> getSuperMap(List<ClassEntry> libraryEntries, + List<ClassEntry> classEntries) { + int size = libraryEntries.size() + classEntries.size(); + Map<String, String> map = new HashMap<String, String>(size); + + SuperclassVisitor visitor = new SuperclassVisitor(map); + addSuperClasses(visitor, libraryEntries); + addSuperClasses(visitor, classEntries); + + return map; + } + + private void addSuperClasses(SuperclassVisitor visitor, List<ClassEntry> entries) { + for (ClassEntry entry : entries) { + ClassReader reader = new ClassReader(entry.bytes); + int flags = ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES; + reader.accept(visitor, flags); + } + } + + /** Visitor skimming classes and initializing a map of super classes */ + private static class SuperclassVisitor extends ClassVisitor { + private final Map<String, String> mMap; + + public SuperclassVisitor(Map<String, String> map) { + super(ASM4); + mMap = map; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, + String[] interfaces) { + if (superName != null) { + mMap.put(name, superName); + } + } + } + + private void findClasses( + @NonNull List<ClassEntry> entries, + @NonNull List<File> classPath) { + for (File classPathEntry : classPath) { if (classPathEntry.getName().endsWith(DOT_JAR)) { File jarFile = classPathEntry; try { @@ -806,11 +931,12 @@ public class LintDriver { while (entry != null) { String name = entry.getName(); if (name.endsWith(DOT_CLASS)) { - byte[] b = ByteStreams.toByteArray(zis); try { - File file = new File(entry.getName()); - checkClassFile(b, project, main, file, jarFile, jarFile, - checks); + byte[] bytes = ByteStreams.toByteArray(zis); + if (bytes != null) { + File file = new File(entry.getName()); + entries.add(new ClassEntry(file, jarFile, jarFile, bytes)); + } } catch (Exception e) { mClient.log(e, null); continue; @@ -828,63 +954,52 @@ public class LintDriver { } continue; - } - - File binDir = classPathEntry; - List<File> classFiles = new ArrayList<File>(); - addClassFiles(binDir, classFiles); + } else if (classPathEntry.isDirectory()) { + File binDir = classPathEntry; + List<File> classFiles = new ArrayList<File>(); + addClassFiles(binDir, classFiles); - for (File file : classFiles) { - try { - byte[] bytes = Files.toByteArray(file); - if (bytes != null) { - checkClassFile(bytes, project, main, file, null /*jarFile*/, binDir, - checks); + for (File file : classFiles) { + try { + byte[] bytes = Files.toByteArray(file); + if (bytes != null) { + entries.add(new ClassEntry(file, null /* jarFile*/, binDir, bytes)); + } + } catch (IOException e) { + mClient.log(e, null); + continue; } - } catch (IOException e) { - mClient.log(e, null); - continue; - } - if (mCanceled) { - return; + if (mCanceled) { + return; + } } + } else { + mClient.log(null, "Ignoring class path entry %1$s", classPathEntry); } } } - private void checkClassFile( - byte[] bytes, - @NonNull Project project, - @Nullable Project main, - @NonNull File file, - @NonNull File jarFile, - @NonNull File binDir, - @NonNull List<Detector> checks) { - ClassReader reader = new ClassReader(bytes); - ClassNode classNode = new ClassNode(); - reader.accept(classNode, 0 /*flags*/); - - if (isSuppressed(null, classNode)) { - // Class was annotated with suppress all -- no need to look any further - return; - } - - ClassContext context = new ClassContext(this, project, main, file, jarFile, - binDir, bytes, classNode); - for (Detector detector : checks) { - if (detector.appliesTo(context, file)) { - fireEvent(EventType.SCANNING_FILE, context); - detector.beforeCheckFile(context); + private void runClassDetectors(ClassContext context, @NonNull List<Detector> checks) { + try { + File file = context.file; + ClassNode classNode = context.getClassNode(); + for (Detector detector : checks) { + if (detector.appliesTo(context, file)) { + fireEvent(EventType.SCANNING_FILE, context); + detector.beforeCheckFile(context); - Detector.ClassScanner scanner = (Detector.ClassScanner) detector; - scanner.checkClass(context, classNode); - detector.afterCheckFile(context); - } + Detector.ClassScanner scanner = (Detector.ClassScanner) detector; + scanner.checkClass(context, classNode); + detector.afterCheckFile(context); + } - if (mCanceled) { - return; + if (mCanceled) { + return; + } } + } catch (Exception e) { + mClient.log(e, null); } } @@ -1161,8 +1276,8 @@ public class LintDriver { @Override - public void log(@Nullable Throwable exception, @Nullable String format, - @Nullable Object... args) { + public void log(@NonNull Severity severity, @Nullable Throwable exception, + @Nullable String format, @Nullable Object... args) { mDelegate.log(exception, format, args); } @@ -1185,6 +1300,11 @@ public class LintDriver { } @Override + public List<File> getJavaLibraries(Project project) { + return mDelegate.getJavaLibraries(project); + } + + @Override @Nullable public IDomParser getDomParser() { return mDelegate.getDomParser(); @@ -1502,4 +1622,20 @@ public class LintDriver { return false; } + + /** A pending class to be analyzed by {@link #checkClasses} */ + private static class ClassEntry { + public final File file; + public final File jarFile; + public final File binDir; + public final byte[] bytes; + + public ClassEntry(File file, File jarFile, File binDir, byte[] bytes) { + super(); + this.file = file; + this.jarFile = jarFile; + this.binDir = binDir; + this.bytes = bytes; + } + } } diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/XmlVisitor.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/XmlVisitor.java index ec6bd6a..c76d44d 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/XmlVisitor.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/XmlVisitor.java @@ -181,7 +181,11 @@ class XmlVisitor { NamedNodeMap attributes = element.getAttributes(); for (int i = 0, n = attributes.getLength(); i < n; i++) { Attr attribute = (Attr) attributes.item(i); - List<Detector.XmlScanner> list = mAttributeToCheck.get(attribute.getLocalName()); + String name = attribute.getLocalName(); + if (name == null) { + name = attribute.getName(); + } + List<Detector.XmlScanner> list = mAttributeToCheck.get(name); if (list != null) { for (int j = 0, max = list.size(); j < max; j++) { Detector.XmlScanner check = list.get(j); diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Category.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Category.java index a318fc7..75aaa0b 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Category.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Category.java @@ -137,31 +137,31 @@ public final class Category implements Comparable<Category> { } /** Issues related to correctness */ - public static final Category CORRECTNESS = Category.create("Correctness", 10); + public static final Category CORRECTNESS = Category.create("Correctness", 100); /** Issues related to security */ - public static final Category SECURITY = Category.create("Security", 9); + public static final Category SECURITY = Category.create("Security", 90); /** Issues related to performance */ - public static final Category PERFORMANCE = Category.create("Performance", 8); + public static final Category PERFORMANCE = Category.create("Performance", 80); /** Issues related to usability */ - public static final Category USABILITY = Category.create("Usability", 7); + public static final Category USABILITY = Category.create("Usability", 70); /** Issues related to accessibility */ - public static final Category A11Y = Category.create("Accessibility", 6); + public static final Category A11Y = Category.create("Accessibility", 60); /** Issues related to internationalization */ - public static final Category I18N = Category.create("Internationalization", 5); + public static final Category I18N = Category.create("Internationalization", 50); // Sub categories /** Issues related to icons */ - public static final Category ICONS = Category.create(USABILITY, "Icons", null, 7); + public static final Category ICONS = Category.create(USABILITY, "Icons", null, 73); /** Issues related to typography */ - public static final Category TYPOGRAPHY = Category.create(USABILITY, "Typography", null, 8); + public static final Category TYPOGRAPHY = Category.create(USABILITY, "Typography", null, 76); /** Issues related to messages/strings */ - public static final Category MESSAGES = Category.create(CORRECTNESS, "Messages", null, 10); + public static final Category MESSAGES = Category.create(CORRECTNESS, "Messages", null, 95); } diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java index 8a4fb2d..a2899d8 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java @@ -52,21 +52,27 @@ public class ClassContext extends Context { private boolean mSearchedForSource; /** If the file is a relative path within a jar file, this is the jar file, otherwise null */ private final File mJarFile; + /** Whether this class is part of a library (rather than corresponding to one of the + * source files in this project */ + private final boolean mFromLibrary; /** * Construct a new {@link ClassContext} * * @param driver the driver running through the checks * @param project the project containing the file being checked - * @param main the main project if this project is a library project, or null if this - * is not a library project. The main project is the root project of all - * library projects, not necessarily the directly including project. + * @param main the main project if this project is a library project, or + * null if this is not a library project. The main project is the + * root project of all library projects, not necessarily the + * directly including project. * @param file the file being checked - * @param jarFile If the file is a relative path within a jar file, this is the jar - * file, otherwise null + * @param jarFile If the file is a relative path within a jar file, this is + * the jar file, otherwise null * @param binDir the root binary directory containing this .class file. * @param bytes the bytecode raw data * @param classNode the bytecode object model + * @param fromLibrary whether this class is from a library rather than part + * of this project */ public ClassContext( @NonNull LintDriver driver, @@ -76,12 +82,14 @@ public class ClassContext extends Context { @Nullable File jarFile, @NonNull File binDir, @NonNull byte[] bytes, - @NonNull ClassNode classNode) { + @NonNull ClassNode classNode, + boolean fromLibrary) { super(driver, project, main, file); mJarFile = jarFile; mBinDir = binDir; mBytes = bytes; mClassNode = classNode; + mFromLibrary = fromLibrary; } /** @@ -116,6 +124,15 @@ public class ClassContext extends Context { } /** + * Returns whether this class is part of a library (not this project). + * + * @return true if this class is part of a library + */ + public boolean isFromClassLibrary() { + return mFromLibrary; + } + + /** * Returns the source file for this class file, if possible. * * @return the source file, or null diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintConstants.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintConstants.java index e1e3c54..9d9cf6d 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintConstants.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintConstants.java @@ -41,7 +41,7 @@ public class LintConstants { public static final String TAG_SERVICE = "service"; //$NON-NLS-1$ public static final String TAG_USES_PERMISSION = "uses-permission";//$NON-NLS-1$ public static final String TAG_APPLICATION = "application"; //$NON-NLS-1$ - public static final String TAG_INTENT_FILTER = " intent-filter"; //$NON-NLS-1$ + public static final String TAG_INTENT_FILTER = "intent-filter"; //$NON-NLS-1$ public static final String TAG_USES_SDK = "uses-sdk"; //$NON-NLS-1$ public static final String TAG_ACTIVITY = "activity"; //$NON-NLS-1$ public static final String TAG_GRANT_PERMISSION = "grant-uri-permission"; //$NON-NLS-1$ @@ -250,6 +250,7 @@ public class LintConstants { public static final String ANDROID_STRING_RESOURCE_PREFIX = "@android:string/"; //$NON-NLS-1$ // Packages + public static final String ANDROID_PKG_PREFIX = "android."; //$NON-NLS-1$ public static final String WIDGET_PKG_PREFIX = "android.widget."; //$NON-NLS-1$ public static final String VIEW_PKG_PREFIX = "android.view."; //$NON-NLS-1$ diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Location.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Location.java index d18ab71..9d61047 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Location.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Location.java @@ -35,6 +35,7 @@ public class Location { private final Position mEnd; private String mMessage; private Location mSecondary; + private Object mClientData; /** * (Private constructor, use one of the factory methods @@ -143,6 +144,28 @@ public class Location { return mMessage; } + /** + * Sets the client data associated with this location. This is an optional + * field which can be used by the creator of the {@link Location} to store + * temporary state associated with the location. + * + * @param clientData the data to store with this location + */ + public void setClientData(@Nullable Object clientData) { + mClientData = clientData; + } + + /** + * Returns the client data associated with this location - an optional field + * which can be used by the creator of the {@link Location} to store + * temporary state associated with the location. + * + * @return the data associated with this location + */ + @Nullable + public Object getClientData() { + return mClientData; + } @Override public String toString() { @@ -352,6 +375,25 @@ public class Location { */ @NonNull Location resolve(); + + /** + * Sets the client data associated with this location. This is an optional + * field which can be used by the creator of the {@link Location} to store + * temporary state associated with the location. + * + * @param clientData the data to store with this location + */ + public void setClientData(@Nullable Object clientData); + + /** + * Returns the client data associated with this location - an optional field + * which can be used by the creator of the {@link Location} to store + * temporary state associated with the location. + * + * @return the data associated with this location + */ + @Nullable + public Object getClientData(); } /** A default {@link Handle} implementation for simple file offsets */ @@ -360,6 +402,7 @@ public class Location { private String mContents; private int mStartOffset; private int mEndOffset; + private Object mClientData; /** * Constructs a new {@link DefaultLocationHandle} @@ -380,5 +423,16 @@ public class Location { public Location resolve() { return Location.create(mFile, mContents, mStartOffset, mEndOffset); } + + @Override + public void setClientData(@Nullable Object clientData) { + mClientData = clientData; + } + + @Override + @Nullable + public Object getClientData() { + return mClientData; + } } } diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Project.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Project.java index e2795ed..910ee2f 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Project.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Project.java @@ -82,6 +82,7 @@ public class Project { private List<File> mFiles; private List<File> mJavaSourceFolders; private List<File> mJavaClassFolders; + private List<File> mJavaLibraries; private List<Project> mDirectLibraries; private List<Project> mAllLibraries; @@ -268,6 +269,28 @@ public class Project { } /** + * Returns the list of Java libraries (typically .jar files) that this + * project depends on. Note that this refers to jar libraries, not Android + * library projects which are processed in a separate pass with their own + * source and class folders. + * + * @return a list of .jar files (or class folders) that this project depends + * on. + */ + @NonNull + public List<File> getJavaLibraries() { + if (mJavaLibraries == null) { + // AOSP builds already merge libraries and class folders into + // the single classes.jar file, so these have already been processed + // in getJavaClassFolders. + + mJavaLibraries = mClient.getJavaLibraries(this); + } + + return mJavaLibraries; + } + + /** * Returns the relative path of a given file relative to the user specified * directory (which is often the project directory but sometimes a higher up * directory when a directory tree is being scanned diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java index 9e5ed81..4b9da60 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Scope.java @@ -77,7 +77,8 @@ public enum Scope { PROGUARD_FILE, /** - * The analysis considers classes in the libraries for this project. + * The analysis considers classes in the libraries for this project. These + * will be analyzed before the classes themselves. */ JAVA_LIBRARIES; diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Severity.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Severity.java index e2add3a..d0bbd7b 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Severity.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Severity.java @@ -28,8 +28,13 @@ import com.google.common.annotations.Beta; @Beta public enum Severity { /** - * Errors: Use sparingly because a warning marked as an error will be - * considered fatal and will abort Export APK etc in ADT + * Fatal: Use sparingly because a warning marked as fatal will be + * considered critical and will abort Export APK etc in ADT + */ + FATAL("Fatal"), + + /** + * Errors: The issue is known to be a real error that must be addressed. */ ERROR("Error"), diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java index 7053038..9e744e4 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java @@ -76,7 +76,7 @@ public class ApiDetector extends LayoutDetector implements Detector.ClassScanner 6, Severity.ERROR, ApiDetector.class, - EnumSet.of(Scope.CLASS_FILE, Scope.JAVA_LIBRARIES, Scope.RESOURCE_FILE)); + EnumSet.of(Scope.CLASS_FILE, Scope.RESOURCE_FILE)); private ApiLookup mApiDatabase; private int mMinApi = -1; @@ -151,7 +151,7 @@ public class ApiDetector extends LayoutDetector implements Detector.ClassScanner // ---- Implements ClassScanner ---- - @SuppressWarnings({"rawtypes","unchecked"}) + @SuppressWarnings("rawtypes") @Override public void checkClass(final ClassContext context, ClassNode classNode) { if (mApiDatabase == null) { @@ -165,7 +165,7 @@ public class ApiDetector extends LayoutDetector implements Detector.ClassScanner // Workaround for the fact that beforeCheckProject is too early int classMinSdk = getLocalMinSdk(classNode.invisibleAnnotations); if (classMinSdk == -1) { - classMinSdk = getMinSdk(context);; + classMinSdk = getMinSdk(context); } List methodList = classNode.methods; @@ -232,24 +232,31 @@ public class ApiDetector extends LayoutDetector implements Detector.ClassScanner String owner = node.owner; String desc = node.desc; - // Handle inherited methods. This does not work if there are multiple - // local classes in the inheritance chain. To solve this in general we'll - // need to make two passes: the first one to gather inheritance information - // for local classes, and then perform the check here resolving up to - // the API classes. + // No need to check methods in this local class; we know they + // won't be an API match if (node.getOpcode() == Opcodes.INVOKEVIRTUAL && owner.equals(classNode.name)) { owner = classNode.superName; } - int api = mApiDatabase.getCallVersion(owner, name, desc); - if (api > minSdk) { - String fqcn = owner.replace('/', '.') + '#' + name; - String message = String.format( - "Call requires API level %1$d (current min is %2$d): %3$s", - api, minSdk, fqcn); - report(context, message, node, method, name, null); - } + do { + int api = mApiDatabase.getCallVersion(owner, name, desc); + if (api > minSdk) { + String fqcn = owner.replace('/', '.') + '#' + name; + String message = String.format( + "Call requires API level %1$d (current min is %2$d): %3$s", + api, minSdk, fqcn); + report(context, message, node, method, name, null); + } + + // For virtual dispatch, walk up the inheritance chain checking + // each inherited method + if (node.getOpcode() == Opcodes.INVOKEVIRTUAL) { + owner = context.getDriver().getSuperClass(owner); + } else { + owner = null; + } + } while (owner != null); } else if (type == AbstractInsnNode.FIELD_INSN) { FieldInsnNode node = (FieldInsnNode) instruction; String name = node.name; diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiLookup.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiLookup.java index 02e31ab..0c3aaee 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiLookup.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiLookup.java @@ -546,6 +546,22 @@ public class ApiLookup { } /** + * Quick determination whether a given class name is possibly interesting; this + * is a quick package prefix check to determine whether we need to consider + * the class at all. This let's us do less actual searching for the vast majority + * of APIs (in libraries, application code etc) that have nothing to do with the + * APIs in our packages. + * @param name the class name in VM format (e.g. using / instead of .) + * @return true if the owner is <b>possibly</b> relevant + */ + public boolean isRelevantClass(String name) { + // TODO: Add quick switching here. This is tied to the database file so if + // we end up with unexpected prefixes there, this could break. For that reason, + // for now we consider everything relevant. + return true; + } + + /** * Returns the API version required by the given class reference, * or -1 if this is not a known API class. Note that it may return -1 * for classes introduced in version 1; internally the database only @@ -558,6 +574,10 @@ public class ApiLookup { * it's unknown <b>or version 1</b>. */ public int getClassVersion(@NonNull String className) { + if (!isRelevantClass(className)) { + return -1; + } + if (mData != null) { int classNumber = findClass(className); if (classNumber != -1) { @@ -601,6 +621,10 @@ public class ApiLookup { @NonNull String owner, @NonNull String name, @NonNull String desc) { + if (!isRelevantClass(owner)) { + return -1; + } + if (mData != null) { int classNumber = findClass(owner); if (classNumber != -1) { @@ -637,6 +661,10 @@ public class ApiLookup { public int getFieldVersion( @NonNull String owner, @NonNull String name) { + if (!isRelevantClass(owner)) { + return -1; + } + if (mData != null) { int classNumber = findClass(owner); if (classNumber != -1) { diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java index ed5586d..e3deb3f 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java @@ -53,7 +53,8 @@ public class BuiltinIssueRegistry extends IssueRegistry { private static final List<Issue> sIssues; static { - List<Issue> issues = new ArrayList<Issue>(60); + final int initialCapacity = 77; + List<Issue> issues = new ArrayList<Issue>(initialCapacity); issues.add(AccessibilityDetector.ISSUE); issues.add(MathDetector.ISSUE); @@ -97,6 +98,7 @@ public class BuiltinIssueRegistry extends IssueRegistry { issues.add(HardcodedDebugModeDetector.ISSUE); issues.add(ManifestOrderDetector.ORDER); issues.add(ManifestOrderDetector.USES_SDK); + issues.add(ManifestOrderDetector.MULTIPLE_USES_SDK); issues.add(SecurityDetector.EXPORTED_SERVICE); issues.add(SecurityDetector.OPEN_PROVIDER); issues.add(SecurityDetector.WORLD_WRITEABLE); @@ -125,7 +127,14 @@ public class BuiltinIssueRegistry extends IssueRegistry { issues.add(ViewTypeDetector.ISSUE); issues.add(WrongImportDetector.ISSUE); issues.add(ViewConstructorDetector.ISSUE); + issues.add(NamespaceDetector.CUSTOMVIEW); + issues.add(NamespaceDetector.UNUSED); + issues.add(NamespaceDetector.TYPO); issues.add(AlwaysShowActionDetector.ISSUE); + issues.add(JavaPerformanceDetector.PAINT_ALLOC); + issues.add(JavaPerformanceDetector.USE_SPARSEARRAY); + + assert initialCapacity >= issues.size() : issues.size(); addCustomIssues(issues); diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/DetectMissingPrefix.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/DetectMissingPrefix.java index d0cf2d4..6ea7899 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/DetectMissingPrefix.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/DetectMissingPrefix.java @@ -16,6 +16,7 @@ package com.android.tools.lint.checks; +import static com.android.tools.lint.detector.api.LintConstants.ANDROID_PKG_PREFIX; import static com.android.tools.lint.detector.api.LintConstants.ATTR_CLASS; import static com.android.tools.lint.detector.api.LintConstants.ATTR_LAYOUT; import static com.android.tools.lint.detector.api.LintConstants.ATTR_STYLE; @@ -114,6 +115,6 @@ public class DetectMissingPrefix extends LayoutDetector { return true; } - return tag.indexOf('.') != -1 && !tag.startsWith("android."); //$NON-NLS-1$ + return tag.indexOf('.') != -1 && !tag.startsWith(ANDROID_PKG_PREFIX); } } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/DuplicateIdDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/DuplicateIdDetector.java index 9c8ddea..7253009 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/DuplicateIdDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/DuplicateIdDetector.java @@ -393,7 +393,7 @@ public class DuplicateIdDetector extends LayoutDetector { @Override public void visitAttribute(XmlContext context, Attr attribute) { - assert attribute.getLocalName().equals(ATTR_ID); + assert attribute.getName().equals(ATTR_ID) || attribute.getLocalName().equals(ATTR_ID); String id = attribute.getValue(); if (mIds.contains(id)) { Location location = context.getLocation(attribute); diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/FieldGetterDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/FieldGetterDetector.java index 8d401e1..8bbc749 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/FieldGetterDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/FieldGetterDetector.java @@ -68,7 +68,7 @@ public class FieldGetterDetector extends Detector implements Detector.ClassScann setEnabledByDefault(false).setMoreInfo( "http://developer.android.com/guide/practices/design/performance.html#internal_get_set"); //$NON-NLS-1$ - /** Constructs a new accessibility check */ + /** Constructs a new {@link FieldGetterDetector} check */ public FieldGetterDetector() { } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/GridLayoutDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/GridLayoutDetector.java index a2788ab..762a4b5 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/GridLayoutDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/GridLayoutDetector.java @@ -51,11 +51,11 @@ public class GridLayoutDetector extends LayoutDetector { "of a GridLayout's rowCount or columnCount is usually an unintentional error.", Category.CORRECTNESS, 4, - Severity.ERROR, + Severity.FATAL, GridLayoutDetector.class, Scope.RESOURCE_FILE_SCOPE); - /** Constructs a new accessibility check */ + /** Constructs a new {@link GridLayoutDetector} check */ public GridLayoutDetector() { } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java index 00df53c..42ed1b8 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/IconDetector.java @@ -253,7 +253,7 @@ public class IconDetector extends Detector implements Detector.XmlScanner { private String mApplicationIcon; - /** Constructs a new accessibility check */ + /** Constructs a new {@link IconDetector} check */ public IconDetector() { } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/JavaPerformanceDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/JavaPerformanceDetector.java new file mode 100644 index 0000000..86242e0 --- /dev/null +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/JavaPerformanceDetector.java @@ -0,0 +1,454 @@ +/* + * Copyright (C) 2012 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.tools.lint.checks; + +import com.android.tools.lint.detector.api.Category; +import com.android.tools.lint.detector.api.Context; +import com.android.tools.lint.detector.api.Detector; +import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.JavaContext; +import com.android.tools.lint.detector.api.Scope; +import com.android.tools.lint.detector.api.Severity; +import com.android.tools.lint.detector.api.Speed; +import com.google.common.collect.Sets; +import com.google.common.collect.Sets.SetView; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; + +import lombok.ast.AstVisitor; +import lombok.ast.BinaryExpression; +import lombok.ast.BinaryOperator; +import lombok.ast.ConstructorInvocation; +import lombok.ast.Expression; +import lombok.ast.ForwardingAstVisitor; +import lombok.ast.If; +import lombok.ast.MethodDeclaration; +import lombok.ast.MethodInvocation; +import lombok.ast.Node; +import lombok.ast.StrictListAccessor; +import lombok.ast.Throw; +import lombok.ast.TypeReference; +import lombok.ast.TypeReferencePart; +import lombok.ast.UnaryExpression; +import lombok.ast.VariableDefinition; +import lombok.ast.VariableReference; + +/** + * Looks for performance issues in Java files, such as memory allocations during + * drawing operations and using HashMap instead of SparseArray. + */ +public class JavaPerformanceDetector extends Detector implements Detector.JavaScanner { + /** Allocating objects during a paint method */ + public static final Issue PAINT_ALLOC = Issue.create( + "DrawAllocation", //$NON-NLS-1$ + "Looks for memory allocations within drawing code", + + "You should avoid allocating objects during a drawing or layout operation. These " + + "are called frequently, so a smooth UI can be interrupted by garbage collection " + + "pauses caused by the object allocations.\n" + + "\n" + + "The way this is generally handled is to allocate the needed objects up front " + + "and to reuse them for each drawing operation.\n" + + "\n" + + "Some methods allocate memory on your behalf (such as Bitmap.create), and these " + + "should be handled in the same way.", + + Category.PERFORMANCE, + 9, + Severity.WARNING, + JavaPerformanceDetector.class, + Scope.JAVA_FILE_SCOPE); + + /** Using HashMaps where SparseArray would be better */ + public static final Issue USE_SPARSEARRAY = Issue.create( + "UseSparseArrays", //$NON-NLS-1$ + "Looks for opportunities to replace HashMaps with the more efficient SparseArray", + + "For maps where the keys are of type integer, it's typically more efficient to " + + "use the Android SparseArray API. This check identifies scenarios where you might " + + "want to consider using SparseArray instead of HashMap for better performance.\n" + + "\n" + + "This is *particularly* useful when the value types are primitives like ints, " + + "where you can use SparseIntArray and avoid auto-boxing the values from int to " + + "Integer.\n" + + "\n" + + "If you need to construct a HashMap because you need to call an API outside of " + + "your control which requires a Map, you can suppress this warning using for " + + "example the @SuppressLint annotation.", + + Category.PERFORMANCE, + 4, + Severity.WARNING, + JavaPerformanceDetector.class, + Scope.JAVA_FILE_SCOPE); + + private static final String INT = "int"; //$NON-NLS-1$ + private static final String INTEGER = "Integer"; //$NON-NLS-1$ + private static final String BOOL = "boolean"; //$NON-NLS-1$ + private static final String BOOLEAN = "Boolean"; //$NON-NLS-1$ + private static final String LONG = "Long"; //$NON-NLS-1$ + private static final String HASH_MAP = "HashMap"; //$NON-NLS-1$ + private static final String CANVAS = "Canvas"; //$NON-NLS-1$ + private static final String ON_DRAW = "onDraw"; //$NON-NLS-1$ + private static final String ON_LAYOUT = "onLayout"; //$NON-NLS-1$ + private static final String ON_MEASURE = "onMeasure"; //$NON-NLS-1$ + + /** Constructs a new {@link JavaPerformanceDetector} check */ + public JavaPerformanceDetector() { + } + + @Override + public boolean appliesTo(Context context, File file) { + return true; + } + + @Override + public Speed getSpeed() { + return Speed.FAST; + } + + // ---- Implements JavaScanner ---- + + @Override + public List<Class<? extends Node>> getApplicableNodeTypes() { + List<Class<? extends Node>> types = new ArrayList<Class<? extends Node>>(3); + types.add(ConstructorInvocation.class); + types.add(MethodDeclaration.class); + types.add(MethodInvocation.class); + return types; + } + + @Override + public AstVisitor createJavaVisitor(JavaContext context) { + return new PerformanceVisitor(context); + } + + private static class PerformanceVisitor extends ForwardingAstVisitor { + private final JavaContext mContext; + /** Whether allocations should be "flagged" in the current method */ + private boolean mFlagAllocations; + private boolean mCheckMaps; + private boolean mCheckAllocations; + + + public PerformanceVisitor(JavaContext context) { + mContext = context; + + mCheckAllocations = context.isEnabled(PAINT_ALLOC); + mCheckMaps = context.isEnabled(USE_SPARSEARRAY); + assert mCheckAllocations || mCheckMaps; // enforced by infrastructure + } + + @Override + public boolean visitMethodDeclaration(MethodDeclaration node) { + mFlagAllocations = isBlockedAllocationMethod(node); + + return super.visitMethodDeclaration(node); + } + + @Override + public boolean visitConstructorInvocation(ConstructorInvocation node) { + if (mCheckMaps) { + TypeReference reference = node.astTypeReference(); + String typeName = reference.astParts().last().astIdentifier().astValue(); + // TODO: Should we handle factory method constructions of HashMaps as well, + // e.g. via Guava? This is a bit trickier since we need to infer the type + // arguments from the calling context. + if (typeName.equals(HASH_MAP)) { + checkSparseArray(node, reference); + } + } + + if (mFlagAllocations && !(node.getParent() instanceof Throw) && mCheckAllocations) { + // Make sure we're still inside the method declaration that marked + // mInDraw as true, in case we've left it and we're in a static + // block or something: + Node method = node; + while (method != null) { + if (method instanceof MethodDeclaration) { + break; + } + method = method.getParent(); + } + if (method != null && isBlockedAllocationMethod(((MethodDeclaration) method)) + && !isLazilyInitialized(node)) { + reportAllocation(node); + } + } + + return super.visitConstructorInvocation(node); + } + + private void reportAllocation(Node node) { + mContext.report(PAINT_ALLOC, node, mContext.getLocation(node), + "Avoid object allocations during draw/layout operations (preallocate and " + + "reuse insteaD)", null); + } + + @Override + public boolean visitMethodInvocation(MethodInvocation node) { + if (mFlagAllocations) { + // Look for forbidden methods + String methodName = node.astName().astValue(); + if (methodName.equals("createBitmap") //$NON-NLS-1$ + || methodName.equals("createScaledBitmap")) { //$NON-NLS-1$ + String operand = node.astOperand().toString(); + if (operand.equals("Bitmap") //$NON-NLS-1$ + || operand.equals("android.graphics.Bitmap")) { //$NON-NLS-1$ + if (!isLazilyInitialized(node)) { + reportAllocation(node); + } + } + } else if (methodName.startsWith("decode")) { //$NON-NLS-1$ + // decodeFile, decodeByteArray, ... + String operand = node.astOperand().toString(); + if (operand.equals("BitmapFactory") //$NON-NLS-1$ + || operand.equals("android.graphics.BitmapFactory")) { //$NON-NLS-1$ + if (!isLazilyInitialized(node)) { + reportAllocation(node); + } + } + } else if (methodName.equals("getClipBounds")) { //$NON-NLS-1$ + if (node.astArguments().isEmpty()) { + mContext.report(PAINT_ALLOC, node, mContext.getLocation(node), + "Avoid object allocations during draw operations: Use " + + "Canvas.getClipBounds(Rect) instead of Canvas.getClipBounds() " + + "which allocates a temporary Rect", null); + } + } + } + + return super.visitMethodInvocation(node); + } + + /** + * Check whether the given invocation is done as a lazy initialization, + * e.g. {@code if (foo == null) foo = new Foo();}. + * <p> + * This tries to also handle the scenario where the check is on some + * <b>other</b> variable - e.g. + * <pre> + * if (foo == null) { + * foo == init1(); + * bar = new Bar(); + * } + * </pre> + * or + * <pre> + * if (!initialized) { + * initialized = true; + * bar = new Bar(); + * } + * </pre> + */ + private boolean isLazilyInitialized(Node node) { + Node curr = node.getParent(); + while (curr != null) { + if (curr instanceof MethodDeclaration) { + return false; + } else if (curr instanceof If) { + If ifNode = (If) curr; + // See if the if block represents a lazy initialization: + // compute all variable names seen in the condition + // (e.g. for "if (foo == null || bar != foo)" the result is "foo,bar"), + // and then compute all variables assigned to in the if body, + // and if there is an overlap, we'll consider the whole if block + // guarded (so lazily initialized and an allocation we won't complain + // about.) + List<String> assignments = new ArrayList<String>(); + AssignmentTracker visitor = new AssignmentTracker(assignments); + ifNode.astStatement().accept(visitor); + if (assignments.size() > 0) { + List<String> references = new ArrayList<String>(); + addReferencedVariables(references, ifNode.astCondition()); + if (references.size() > 0) { + SetView<String> intersection = Sets.intersection( + new HashSet<String>(assignments), + new HashSet<String>(references)); + return intersection.size() > 0; + } + } + return false; + + } + curr = curr.getParent(); + } + + return false; + } + + /** Adds any variables referenced in the given expression into the given list */ + private static void addReferencedVariables(Collection<String> variables, + Expression expression) { + if (expression instanceof BinaryExpression) { + BinaryExpression binary = (BinaryExpression) expression; + addReferencedVariables(variables, binary.astLeft()); + addReferencedVariables(variables, binary.astRight()); + } else if (expression instanceof UnaryExpression) { + UnaryExpression unary = (UnaryExpression) expression; + addReferencedVariables(variables, unary.astOperand()); + } else if (expression instanceof VariableReference) { + VariableReference reference = (VariableReference) expression; + variables.add(reference.astIdentifier().astValue()); + } + } + + /** + * Returns whether the given method declaration represents a method + * where allocating objects is not allowed for performance reasons + */ + private boolean isBlockedAllocationMethod(MethodDeclaration node) { + return isOnDrawMethod(node) || isOnMeasureMethod(node) || isOnLayoutMethod(node); + } + + /** + * Returns true if this method looks like it's overriding android.view.View's + * {@code protected void onDraw(Canvas canvas)} + */ + private static boolean isOnDrawMethod(MethodDeclaration node) { + if (ON_DRAW.equals(node.astMethodName().astValue())) { + StrictListAccessor<VariableDefinition, MethodDeclaration> parameters = + node.astParameters(); + if (parameters != null && parameters.size() == 1) { + VariableDefinition arg0 = parameters.first(); + TypeReferencePart type = arg0.astTypeReference().astParts().last(); + String typeName = type.getTypeName(); + if (typeName.equals(CANVAS)) { + return true; + } + } + } + + return false; + } + + /** + * Returns true if this method looks like it's overriding + * android.view.View's + * {@code protected void onLayout(boolean changed, int left, int top, + * int right, int bottom)} + */ + private static boolean isOnLayoutMethod(MethodDeclaration node) { + if (ON_LAYOUT.equals(node.astMethodName().astValue())) { + StrictListAccessor<VariableDefinition, MethodDeclaration> parameters = + node.astParameters(); + if (parameters != null && parameters.size() == 5) { + Iterator<VariableDefinition> iterator = parameters.iterator(); + if (!iterator.hasNext()) { + return false; + } + + // Ensure that the argument list matches boolean, int, int, int, int + TypeReferencePart type = iterator.next().astTypeReference().astParts().last(); + if (!type.getTypeName().equals(BOOL) || !iterator.hasNext()) { + return false; + } + for (int i = 0; i < 4; i++) { + type = iterator.next().astTypeReference().astParts().last(); + if (!type.getTypeName().equals(INT)) { + return false; + } + if (!iterator.hasNext()) { + return i == 3; + } + } + } + } + + return false; + } + + /** + * Returns true if this method looks like it's overriding android.view.View's + * {@code protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)} + */ + private static boolean isOnMeasureMethod(MethodDeclaration node) { + if (ON_MEASURE.equals(node.astMethodName().astValue())) { + StrictListAccessor<VariableDefinition, MethodDeclaration> parameters = + node.astParameters(); + if (parameters != null && parameters.size() == 2) { + VariableDefinition arg0 = parameters.first(); + VariableDefinition arg1 = parameters.last(); + TypeReferencePart type1 = arg0.astTypeReference().astParts().last(); + TypeReferencePart type2 = arg1.astTypeReference().astParts().last(); + return INT.equals(type1.getTypeName()) && INT.equals(type2.getTypeName()); + } + } + + return false; + } + + /** + * Checks whether the given constructor call and type reference refers + * to a HashMap constructor call that is eligible for replacement by a + * SparseArray call instead + */ + private void checkSparseArray(ConstructorInvocation node, TypeReference reference) { + // reference.hasTypeArguments returns false where it should not + StrictListAccessor<TypeReference, TypeReference> types = reference.getTypeArguments(); + if (types != null && types.size() == 2) { + TypeReference first = types.first(); + if (first.getTypeName().equals(INTEGER)) { + String valueType = types.last().getTypeName(); + if (valueType.equals(INTEGER)) { + mContext.report(USE_SPARSEARRAY, node, mContext.getLocation(node), + "Use new SparseIntArray(...) instead for better performance", + null); + } else if (valueType.equals(BOOLEAN)) { + mContext.report(USE_SPARSEARRAY, node, mContext.getLocation(node), + "Use new SparseBooleanArray(...) instead for better performance", + null); + } else if (valueType.equals(LONG) && mContext.getProject().getMinSdk() >= 17) { + mContext.report(USE_SPARSEARRAY, node, mContext.getLocation(node), + "Use new SparseLongArray(...) instead for better performance", + null); + } else { + mContext.report(USE_SPARSEARRAY, node, mContext.getLocation(node), + String.format( + "Use new SparseArray<%1$s>(...) instead for better performance", + valueType), + null); + } + } + } + } + } + + /** Visitor which records variable names assigned into */ + private static class AssignmentTracker extends ForwardingAstVisitor { + private final Collection<String> mVariables; + + public AssignmentTracker(Collection<String> variables) { + mVariables = variables; + } + + @Override + public boolean visitBinaryExpression(BinaryExpression node) { + BinaryOperator operator = node.astOperator(); + if (operator == BinaryOperator.ASSIGN || operator == BinaryOperator.OR_ASSIGN) { + mVariables.add(node.astLeft().toString()); + } + + return super.visitBinaryExpression(node); + } + } +} diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java index 144a191..97cb5f6 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java @@ -35,6 +35,7 @@ import com.android.tools.lint.detector.api.Speed; import com.android.tools.lint.detector.api.XmlContext; import org.w3c.dom.Element; +import org.w3c.dom.NodeList; import java.io.File; import java.util.Arrays; @@ -65,7 +66,7 @@ public class ManifestOrderDetector extends Detector implements Detector.XmlScann /** Missing a {@code <uses-sdk>} element */ public static final Issue USES_SDK = Issue.create( - "UsesSdk", //$NON-NLS-1$ + "UsesMinSdkAttributes", //$NON-NLS-1$ "Checks that the minimum SDK and target SDK attributes are defined", "The manifest should contain a <uses-sdk> element which defines the " + @@ -80,12 +81,31 @@ public class ManifestOrderDetector extends Detector implements Detector.XmlScann EnumSet.of(Scope.MANIFEST)).setMoreInfo( "http://developer.android.com/guide/topics/manifest/uses-sdk-element.html"); //$NON-NLS-1$ - /** Constructs a new accessibility check */ + /** Missing a {@code <uses-sdk>} element */ + public static final Issue MULTIPLE_USES_SDK = Issue.create( + "MultipleUsesSdk", //$NON-NLS-1$ + "Checks that the <uses-sdk> element appears at most once", + + "The <uses-sdk> element should appear just once; the tools will *not* merge the " + + "contents of all the elements so if you split up the atttributes across multiple " + + "elements, only one of them will take effect. To fix this, just merge all the " + + "attributes from the various elements into a single <uses-sdk> element.", + + Category.CORRECTNESS, + 6, + Severity.ERROR, + ManifestOrderDetector.class, + EnumSet.of(Scope.MANIFEST)).setMoreInfo( + "http://developer.android.com/guide/topics/manifest/uses-sdk-element.html"); //$NON-NLS-1$ + + /** Constructs a new {@link ManifestOrderDetector} check */ public ManifestOrderDetector() { } private boolean mSeenApplication; - private boolean mSeenUsesSdk; + + /** Number of times we've seen the <uses-sdk> element */ + private int mSeenUsesSdk; @Override public Speed getSpeed() { @@ -100,12 +120,12 @@ public class ManifestOrderDetector extends Detector implements Detector.XmlScann @Override public void beforeCheckFile(Context context) { mSeenApplication = false; - mSeenUsesSdk = false; + mSeenUsesSdk = 0; } @Override public void afterCheckFile(Context context) { - if (!mSeenUsesSdk) { + if (mSeenUsesSdk == 0) { context.report(USES_SDK, Location.create(context.file), "Manifest should specify a minimum API level with " + "<uses-sdk android:minSdkVersion=\"?\" />; if it really supports " + @@ -136,7 +156,31 @@ public class ManifestOrderDetector extends Detector implements Detector.XmlScann public void visitElement(XmlContext context, Element element) { String tag = element.getTagName(); if (tag.equals(TAG_USES_SDK)) { - mSeenUsesSdk = true; + mSeenUsesSdk++; + + if (mSeenUsesSdk == 2) { // Only warn when we encounter the first one + Location location = context.getLocation(element); + + // Link up *all* encountered locations in the document + NodeList elements = element.getOwnerDocument().getElementsByTagName(TAG_USES_SDK); + Location secondary = null; + for (int i = elements.getLength() - 1; i >= 0; i--) { + Element e = (Element) elements.item(i); + if (e != element) { + Location l = context.getLocation(e); + l.setSecondary(secondary); + l.setMessage("Also appears here"); + secondary = l; + } + } + location.setSecondary(secondary); + + context.report(MULTIPLE_USES_SDK, element, location, + "There should only be a single <uses-sdk> element in the manifest:" + + " merge these together", null); + return; + } + if (!element.hasAttributeNS(ANDROID_URI, ATTR_MIN_SDK_VERSION)) { context.report(USES_SDK, element, context.getLocation(element), "<uses-sdk> tag should specify a minimum API level with " + diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/MathDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MathDetector.java index 5266cc4..3c8d5d1 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/MathDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MathDetector.java @@ -63,7 +63,7 @@ public class MathDetector extends Detector implements Detector.ClassScanner { //"http://developer.android.com/reference/android/util/FloatMath.html"); //$NON-NLS-1$ "http://developer.android.com/guide/practices/design/performance.html#avoidfloat"); //$NON-NLS-1$ - /** Constructs a new accessibility check */ + /** Constructs a new {@link MathDetector} check */ public MathDetector() { } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/NamespaceDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/NamespaceDetector.java new file mode 100644 index 0000000..60ae3a3 --- /dev/null +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/NamespaceDetector.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2012 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.tools.lint.checks; + +import static com.android.tools.lint.detector.api.LintConstants.ANDROID_PKG_PREFIX; +import static com.android.tools.lint.detector.api.LintConstants.ANDROID_URI; +import static com.android.tools.lint.detector.api.LintConstants.XMLNS_PREFIX; + +import com.android.tools.lint.detector.api.Category; +import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.LayoutDetector; +import com.android.tools.lint.detector.api.LintUtils; +import com.android.tools.lint.detector.api.Scope; +import com.android.tools.lint.detector.api.Severity; +import com.android.tools.lint.detector.api.Speed; +import com.android.tools.lint.detector.api.XmlContext; + +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.util.HashMap; +import java.util.Map; + +/** + * Checks for various issues related to XML namespaces + */ +public class NamespaceDetector extends LayoutDetector { + /** Typos in the namespace */ + public static final Issue TYPO = Issue.create( + "NamespaceTypo", //$NON-NLS-1$ + "Looks for misspellings in namespace declarations", + + "Accidental misspellings in namespace declarations can lead to some very " + + "obscure error messages. This check looks for potential misspellings to " + + "help track these down.", + Category.CORRECTNESS, + 8, + Severity.WARNING, + NamespaceDetector.class, + Scope.RESOURCE_FILE_SCOPE); + + /** Unused namespace declarations */ + public static final Issue UNUSED = Issue.create( + "UnusedNamespace", //$NON-NLS-1$ + "Finds unused namespaces in XML documents", + + "Unused namespace declarations take up space and require processing that is not " + + "necessary", + + Category.CORRECTNESS, + 1, + Severity.WARNING, + NamespaceDetector.class, + Scope.RESOURCE_FILE_SCOPE); + + /** Using custom namespace attributes in a library project */ + public static final Issue CUSTOMVIEW = Issue.create( + "LibraryCustomView", //$NON-NLS-1$ + "Flags custom views in libraries, which currently do not work", + + "Using a custom view in a library project (where the custom view " + + "requires XML attributes from a custom namespace) does not yet " + + "work.", + Category.CORRECTNESS, + 6, + Severity.ERROR, + NamespaceDetector.class, + Scope.RESOURCE_FILE_SCOPE); + + /** Prefix relevant for custom namespaces */ + private static final String XMLNS_ANDROID = "xmlns:android"; //$NON-NLS-1$ + private static final String XMLNS_A = "xmlns:a"; //$NON-NLS-1$ + private static final String URI_PREFIX = "http://schemas.android.com/apk/res/"; //$NON-NLS-1$ + + private Map<String, Attr> mUnusedNamespaces; + private boolean mCheckUnused; + private boolean mCheckCustomAttrs; + + /** Constructs a new {@link NamespaceDetector} */ + public NamespaceDetector() { + } + + @Override + public Speed getSpeed() { + return Speed.FAST; + } + + @Override + public void visitDocument(XmlContext context, Document document) { + boolean haveCustomNamespace = false; + Element root = document.getDocumentElement(); + NamedNodeMap attributes = root.getAttributes(); + for (int i = 0, n = attributes.getLength(); i < n; i++) { + Node item = attributes.item(i); + if (item.getNodeName().startsWith(XMLNS_PREFIX)) { + String value = item.getNodeValue(); + + if (!value.equals(ANDROID_URI)) { + Attr attribute = (Attr) item; + + if (value.startsWith(URI_PREFIX)) { + haveCustomNamespace = true; + if (mUnusedNamespaces == null) { + mUnusedNamespaces = new HashMap<String, Attr>(); + } + mUnusedNamespaces.put(item.getNodeName().substring(XMLNS_PREFIX.length()), + attribute); + } + + String name = attribute.getName(); + if (!name.equals(XMLNS_ANDROID) && !name.equals(XMLNS_A)) { + continue; + } + + if (!context.isEnabled(TYPO)) { + continue; + } + + if (name.equals(XMLNS_A)) { + // For the "android" prefix we always assume that the namespace prefix + // should be our expected prefix, but for the "a" prefix we make sure + // that it's at least "close"; if you're bound it to something completely + // different, don't complain. + if (LintUtils.editDistance(ANDROID_URI, value) > 4) { + continue; + } + } + + if (value.equalsIgnoreCase(ANDROID_URI)) { + context.report(TYPO, attribute, context.getLocation(attribute), + String.format( + "URI is case sensitive: was \"%1$s\", expected \"%2$s\"", + value, ANDROID_URI), null); + } else { + context.report(TYPO, attribute, context.getLocation(attribute), + String.format( + "Unexpected namespace URI bound to the \"android\" " + + "prefix, was %1$s, expected %2$s", value, ANDROID_URI), + null); + } + } + } + } + + if (haveCustomNamespace) { + mCheckCustomAttrs = context.isEnabled(CUSTOMVIEW) && context.getProject().isLibrary(); + mCheckUnused = context.isEnabled(UNUSED); + checkElement(context, document.getDocumentElement()); + + if (mCheckUnused && mUnusedNamespaces.size() > 0) { + for (Map.Entry<String, Attr> entry : mUnusedNamespaces.entrySet()) { + String prefix = entry.getKey(); + Attr attribute = entry.getValue(); + context.report(UNUSED, attribute, context.getLocation(attribute), + String.format("Unused namespace %1$s", prefix), null); + } + } + } + } + + private void checkElement(XmlContext context, Node node) { + if (node.getNodeType() == Node.ELEMENT_NODE) { + if (mCheckCustomAttrs) { + String tag = node.getNodeName(); + if (tag.indexOf('.') != -1 + // Don't consider android.support.* and android.app.FragmentBreadCrumbs etc + && !tag.startsWith(ANDROID_PKG_PREFIX)) { + NamedNodeMap attributes = ((Element) node).getAttributes(); + for (int i = 0, n = attributes.getLength(); i < n; i++) { + Attr attribute = (Attr) attributes.item(i); + String uri = attribute.getNamespaceURI(); + if (uri != null && uri.length() > 0 && uri.startsWith(URI_PREFIX) + && !uri.equals(ANDROID_URI)) { + context.report(CUSTOMVIEW, attribute, context.getLocation(attribute), + "Using a custom namespace attributes in a library project does " + + "not yet work", null); + } + } + } + } + + if (mCheckUnused) { + NamedNodeMap attributes = ((Element) node).getAttributes(); + for (int i = 0, n = attributes.getLength(); i < n; i++) { + Attr attribute = (Attr) attributes.item(i); + String prefix = attribute.getPrefix(); + if (prefix != null) { + mUnusedNamespaces.remove(prefix); + } + } + } + + NodeList childNodes = node.getChildNodes(); + for (int i = 0, n = childNodes.getLength(); i < n; i++) { + checkElement(context, childNodes.item(i)); + } + } + } +} diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/PrivateResourceDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/PrivateResourceDetector.java index 7f9e859..2b37f82 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/PrivateResourceDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/PrivateResourceDetector.java @@ -43,7 +43,7 @@ public class PrivateResourceDetector extends ResourceXmlDetector { "resources under $ANDROID_SK/platforms/android-$VERSION/data/res/.", Category.CORRECTNESS, 3, - Severity.ERROR, + Severity.FATAL, PrivateResourceDetector.class, Scope.RESOURCE_FILE_SCOPE); diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ProguardDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ProguardDetector.java index 798cf26..95de739 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ProguardDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ProguardDetector.java @@ -46,7 +46,7 @@ public class ProguardDetector extends Detector { "not Java (such as possibly CustomViews) can get deleted.", Category.CORRECTNESS, 8, - Severity.ERROR, + Severity.FATAL, ProguardDetector.class, EnumSet.of(Scope.PROGUARD_FILE)).setMoreInfo( "http://http://code.google.com/p/android/issues/detail?id=16384"); //$NON-NLS-1$ diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/SdCardDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/SdCardDetector.java index 1a765b8..b239d57 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/SdCardDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/SdCardDetector.java @@ -54,7 +54,7 @@ public class SdCardDetector extends Detector implements Detector.JavaScanner { Scope.JAVA_FILE_SCOPE).setMoreInfo( "http://developer.android.com/guide/topics/data/data-storage.html#filesExternal"); //$NON-NLS-1$ - /** Constructs a new accessibility check */ + /** Constructs a new {@link SdCardDetector} check */ public SdCardDetector() { } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/SecurityDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/SecurityDetector.java index 1493131..f19f8aa 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/SecurityDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/SecurityDetector.java @@ -107,7 +107,7 @@ public class SecurityDetector extends Detector implements Detector.XmlScanner, SecurityDetector.class, Scope.JAVA_FILE_SCOPE); - /** Constructs a new accessibility check */ + /** Constructs a new {@link SecurityDetector} check */ public SecurityDetector() { } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/StateListDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/StateListDetector.java index 20fcf6b..3235c9a 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/StateListDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/StateListDetector.java @@ -82,6 +82,9 @@ public class StateListDetector extends ResourceXmlDetector { NamedNodeMap attributes = child.getAttributes(); for (int j = 0; j < attributes.getLength(); j++) { Attr attribute = (Attr) attributes.item(j); + if (attribute.getLocalName() == null) { + continue; + } if (attribute.getLocalName().startsWith("state_")) { hasState = true; break; diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/StringFormatDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/StringFormatDetector.java index 4d1c8ae..665a43b 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/StringFormatDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/StringFormatDetector.java @@ -75,7 +75,8 @@ import lombok.ast.VariableDefinitionEntry; import lombok.ast.VariableReference; /** - * Check which looks for accessibility problems like missing content descriptions + * Check which looks for problems with formatting strings such as inconsistencies between + * translations or between string declaration and string usage in Java. */ public class StringFormatDetector extends ResourceXmlDetector implements Detector.JavaScanner { /** The name of the String.format method */ @@ -149,13 +150,14 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto * defined multiple times, usually for different translations. */ private Map<String, List<Pair<Handle, String>>> mFormatStrings; + /** * List of strings that contain percents that aren't formatting strings; these * should not be passed to String.format. */ private Map<String, Handle> mNotFormatStrings = new HashMap<String, Handle>(); - /** Constructs a new accessibility check */ + /** Constructs a new {@link StringFormatDetector} check */ public StringFormatDetector() { } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/StyleCycleDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/StyleCycleDetector.java index 6856c14..f248d6e 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/StyleCycleDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/StyleCycleDetector.java @@ -48,7 +48,7 @@ public class StyleCycleDetector extends ResourceXmlDetector { "exceptions.", Category.CORRECTNESS, 8, - Severity.ERROR, + Severity.FATAL, StyleCycleDetector.class, Scope.RESOURCE_FILE_SCOPE).setMoreInfo( "http://developer.android.com/guide/topics/ui/themes.html#Inheritance"); //$NON-NLS-1$ diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TranslationDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TranslationDetector.java index 41390d7..efa53ad 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TranslationDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TranslationDetector.java @@ -81,7 +81,7 @@ public class TranslationDetector extends ResourceXmlDetector { "environment variable ANDROID_LINT_COMPLETE_REGIONS.", Category.MESSAGES, 8, - Severity.ERROR, + Severity.FATAL, TranslationDetector.class, Scope.ALL_RESOURCES_SCOPE); diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ViewConstructorDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ViewConstructorDetector.java index ef89232..e5d1e7e 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ViewConstructorDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ViewConstructorDetector.java @@ -67,7 +67,7 @@ public class ViewConstructorDetector extends Detector implements Detector.ClassS ViewConstructorDetector.class, EnumSet.of(Scope.CLASS_FILE)); - /** Constructs a new accessibility check */ + /** Constructs a new {@link ViewConstructorDetector} check */ public ViewConstructorDetector() { } @@ -92,40 +92,55 @@ public class ViewConstructorDetector extends Detector implements Detector.ClassS return; } - String superName = classNode.superName; - if (superName.equals("android/view/View") //$NON-NLS-1$ - || superName.equals("android/view/ViewGroup") //$NON-NLS-1$ - || superName.startsWith("android/widget/") //$NON-NLS-1$ - && !((superName.endsWith("Adapter") //$NON-NLS-1$ - || superName.endsWith("Controller") //$NON-NLS-1$ - || superName.endsWith("Service") //$NON-NLS-1$ - || superName.endsWith("Provider") //$NON-NLS-1$ - || superName.endsWith("Filter")))) { //$NON-NLS-1$ - - // Look through constructors - @SuppressWarnings("rawtypes") - List methods = classNode.methods; - for (Object methodObject : methods) { - MethodNode method = (MethodNode) methodObject; - if (method.name.equals("<init>")) { //$NON-NLS-1$ - String desc = method.desc; - if (desc.equals(SIG1) || desc.equals(SIG2) || desc.equals(SIG3)) { - return; - } - } + if (isViewClass(context, classNode)) { + checkConstructors(context, classNode); + } + } + + private static boolean isViewClass(ClassContext context, ClassNode node) { + String superName = node.superName; + while (superName != null) { + if (superName.equals("android/view/View") //$NON-NLS-1$ + || superName.equals("android/view/ViewGroup") //$NON-NLS-1$ + || superName.startsWith("android/widget/") //$NON-NLS-1$ + && !((superName.endsWith("Adapter") //$NON-NLS-1$ + || superName.endsWith("Controller") //$NON-NLS-1$ + || superName.endsWith("Service") //$NON-NLS-1$ + || superName.endsWith("Provider") //$NON-NLS-1$ + || superName.endsWith("Filter")))) { //$NON-NLS-1$ + return true; } - // If we get this far, none of the expected constructors were found. - - // Use location of one of the constructors? - String message = String.format( - "Custom view %1$s is missing constructor used by tools: " + - "(Context) or (Context,AttributeSet) or (Context,AttributeSet,int)", - classNode.name); - File sourceFile = context.getSourceFile(); - Location location = Location.create(sourceFile != null - ? sourceFile : context.file); - context.report(ISSUE, location, message, null /*data*/); + superName = context.getDriver().getSuperClass(superName); } + + return false; + } + + private void checkConstructors(ClassContext context, ClassNode classNode) { + // Look through constructors + @SuppressWarnings("rawtypes") + List methods = classNode.methods; + for (Object methodObject : methods) { + MethodNode method = (MethodNode) methodObject; + if (method.name.equals("<init>")) { //$NON-NLS-1$ + String desc = method.desc; + if (desc.equals(SIG1) || desc.equals(SIG2) || desc.equals(SIG3)) { + return; + } + } + } + + // If we get this far, none of the expected constructors were found. + + // Use location of one of the constructors? + String message = String.format( + "Custom view %1$s is missing constructor used by tools: " + + "(Context) or (Context,AttributeSet) or (Context,AttributeSet,int)", + classNode.name); + File sourceFile = context.getSourceFile(); + Location location = Location.create(sourceFile != null + ? sourceFile : context.file); + context.report(ISSUE, location, message, null /*data*/); } } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/WrongIdDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/WrongIdDetector.java index 823eabd..656f4de 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/WrongIdDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/WrongIdDetector.java @@ -97,7 +97,7 @@ public class WrongIdDetector extends LayoutDetector { "it.", Category.CORRECTNESS, 8, - Severity.ERROR, + Severity.FATAL, WrongIdDetector.class, Scope.ALL_RESOURCES_SCOPE); @@ -179,6 +179,7 @@ public class WrongIdDetector extends LayoutDetector { XmlContext xmlContext = (XmlContext) context; IDomParser parser = xmlContext.parser; Handle handle = parser.createLocationHandle(xmlContext, attr); + handle.setClientData(attr); if (mHandles == null) { mHandles = new ArrayList<Pair<String,Handle>>(); @@ -205,7 +206,6 @@ public class WrongIdDetector extends LayoutDetector { boolean isBound = idDefined(mGlobalIds, id); if (!isBound && checkExists && projectScope) { Handle handle = pair.getSecond(); - Location location = handle.resolve(); boolean isDeclared = idDefined(mDeclaredIds, id); id = stripIdPrefix(id); String suggestionMessage; @@ -229,25 +229,33 @@ public class WrongIdDetector extends LayoutDetector { "The id \"%1$s\" is not defined anywhere.%2$s", id, suggestionMessage); } - // TODO: Compute applicable node scope - context.report(UNKNOWN_ID, location, message, null); + report(context, UNKNOWN_ID, handle, message); } else if (checkSameLayout && (!projectScope || isBound)) { // The id was defined, but in a different layout. Usually not intentional // (might be referring to a random other view that happens to have the same // name.) Handle handle = pair.getSecond(); - Location location = handle.resolve(); - // TODO: Compute applicable node scope - context.report(UNKNOWN_ID_LAYOUT, location, + report(context, UNKNOWN_ID_LAYOUT, handle, String.format( "The id \"%1$s\" is not referring to any views in this layout", - stripIdPrefix(id)), - null); + stripIdPrefix(id))); } } } } + private void report(Context context, Issue issue, Handle handle, String message) { + Location location = handle.resolve(); + Object clientData = handle.getClientData(); + if (clientData instanceof Attr) { + if (context.getDriver().isSuppressed(issue, (Attr) clientData)) { + return; + } + } + + context.report(issue, location, message, null); + } + @Override public void visitElement(XmlContext context, Element element) { if (element.getTagName().equals(RELATIVE_LAYOUT)) { @@ -272,7 +280,7 @@ public class WrongIdDetector extends LayoutDetector { @Override public void visitAttribute(XmlContext context, Attr attribute) { - assert attribute.getLocalName().equals(ATTR_ID); + assert attribute.getName().equals(ATTR_ID) || attribute.getLocalName().equals(ATTR_ID); String id = attribute.getValue(); mFileIds.add(id); mGlobalIds.add(id); diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/WrongImportDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/WrongImportDetector.java index 59e3314..2aa416c 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/WrongImportDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/WrongImportDetector.java @@ -62,7 +62,7 @@ public class WrongImportDetector extends Detector implements Detector.JavaScanne WrongImportDetector.class, Scope.JAVA_FILE_SCOPE); - /** Constructs a new accessibility check */ + /** Constructs a new {@link WrongImportDetector} check */ public WrongImportDetector() { } diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AbstractCheckTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AbstractCheckTest.java index 36e4106..7f21477 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AbstractCheckTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/AbstractCheckTest.java @@ -298,6 +298,10 @@ public abstract class AbstractCheckTest extends TestCase { } Severity severity = context.getConfiguration().getSeverity(issue); + if (severity == Severity.FATAL) { + // Treat fatal errors like errors in the golden files. + severity = Severity.ERROR; + } sb.append(severity.getDescription()); sb.append(": "); diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ApiDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ApiDetectorTest.java index b3050fe..d3a8aad 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ApiDetectorTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ApiDetectorTest.java @@ -146,6 +146,22 @@ public class ApiDetectorTest extends AbstractCheckTest { )); } + public void testInheritLocal() throws Exception { + // Test virtual dispatch in a local class which extends some other local class (which + // in turn extends an Android API) + assertEquals( + "ApiCallTest3.java:10: Error: Call requires API level 11 (current min is 1): android.app.Activity#getActionBar", + + lintProject( + "apicheck/classpath=>.classpath", + "apicheck/minsdk1.xml=>AndroidManifest.xml", + "apicheck/Intermediate.java.txt=>src/test/pkg/Intermediate.java", + "apicheck/ApiCallTest3.java.txt=>src/test/pkg/ApiCallTest3.java", + "apicheck/ApiCallTest3.class.data=>bin/classes/test/pkg/ApiCallTest3.class", + "apicheck/Intermediate.class.data=>bin/classes/test/pkg/Intermediate.class" + )); + } + // Test suppressing errors -- on classes, methods etc. public void testSuppress() throws Exception { diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/FieldGetterDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/FieldGetterDetectorTest.java index bdb7618..ccbf26b 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/FieldGetterDetectorTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/FieldGetterDetectorTest.java @@ -41,6 +41,21 @@ public class FieldGetterDetectorTest extends AbstractCheckTest { )); } + public void testLibraries() throws Exception { + // This tests the infrastructure: it makes sure that we *don't* run this + // check in jars that are on the jar library dependency path (testJar() checks + // that it *does* work for local jar classes) + assertEquals( + "No warnings.", + + lintProject( + "bytecode/classpath-lib=>.classpath", + "bytecode/AndroidManifest.xml=>AndroidManifest.xml", + "bytecode/GetterTest.java.txt=>src/test/bytecode/GetterTest.java", + "bytecode/GetterTest.jar.data=>libs/library.jar" + )); + } + public void testJar() throws Exception { assertEquals( "GetterTest.java:47: Warning: Calling getter method getFoo1() on self is slower than field access (mFoo1)\n" + diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/JavaPerformanceDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/JavaPerformanceDetectorTest.java new file mode 100644 index 0000000..7853ddc --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/JavaPerformanceDetectorTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2012 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.tools.lint.checks; + +import com.android.tools.lint.detector.api.Detector; + +@SuppressWarnings("javadoc") +public class JavaPerformanceDetectorTest extends AbstractCheckTest { + @Override + protected Detector getDetector() { + return new JavaPerformanceDetector(); + } + + public void test() throws Exception { + assertEquals( + "JavaPerformanceTest.java:103: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse insteaD)\n" + + "JavaPerformanceTest.java:109: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse insteaD)\n" + + "JavaPerformanceTest.java:112: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse insteaD)\n" + + "JavaPerformanceTest.java:113: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse insteaD)\n" + + "JavaPerformanceTest.java:114: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse insteaD)\n" + + "JavaPerformanceTest.java:116: Warning: Avoid object allocations during draw operations: Use Canvas.getClipBounds(Rect) instead of Canvas.getClipBounds() which allocates a temporary Rect\n" + + "JavaPerformanceTest.java:140: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse insteaD)\n" + + "JavaPerformanceTest.java:28: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse insteaD)\n" + + "JavaPerformanceTest.java:29: Warning: Avoid object allocations during draw/layout operations (preallocate and reuse insteaD)\n" + + "JavaPerformanceTest.java:70: Warning: Use new SparseArray<String>(...) instead for better performance\n" + + "JavaPerformanceTest.java:72: Warning: Use new SparseBooleanArray(...) instead for better performance\n" + + "JavaPerformanceTest.java:74: Warning: Use new SparseIntArray(...) instead for better performance", + + lintProject("src/test/pkg/JavaPerformanceTest.java.txt=>" + + "src/test/pkg/JavaPerformanceTest.java")); + } +} diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ManifestOrderDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ManifestOrderDetectorTest.java index cebf793..8ecee91 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ManifestOrderDetectorTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ManifestOrderDetectorTest.java @@ -64,4 +64,17 @@ public class ManifestOrderDetectorTest extends AbstractCheckTest { "missingmin.xml=>AndroidManifest.xml", "res/values/strings.xml")); } + + public void testMultipleSdk() throws Exception { + assertEquals( + "AndroidManifest.xml:7: Warning: <uses-sdk> tag should specify a target API level (the highest verified version; when running on later versions, compatibility behaviors may be enabled) with android:targetSdkVersion=\"?\"\n" + + "AndroidManifest.xml:9: Warning: <uses-sdk> tag should specify a minimum API level with android:minSdkVersion=\"?\"\n" + + "ManifestOrderDetectorTest_testMultipleSdk/AndroidManifest.xml:8: Error: There should only be a single <uses-sdk> element in the manifest: merge these together\n" + + "=> ManifestOrderDetectorTest_testMultipleSdk/AndroidManifest.xml:7: Also appears here\n" + + "=> ManifestOrderDetectorTest_testMultipleSdk/AndroidManifest.xml:9: Also appears here", + + lintProject( + "multiplesdk.xml=>AndroidManifest.xml", + "res/values/strings.xml")); + } } diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/NamespaceDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/NamespaceDetectorTest.java new file mode 100644 index 0000000..3570f5c --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/NamespaceDetectorTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2012 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.tools.lint.checks; + +import com.android.tools.lint.detector.api.Detector; + +@SuppressWarnings("javadoc") +public class NamespaceDetectorTest extends AbstractCheckTest { + @Override + protected Detector getDetector() { + return new NamespaceDetector(); + } + + public void testCustom() throws Exception { + assertEquals( + "customview.xml:16: Error: Using a custom namespace attributes in a library project does not yet work", + + lintProject( + "multiproject/library-manifest.xml=>AndroidManifest.xml", + "multiproject/library.properties=>project.properties", + "res/layout/customview.xml" + )); + } + + public void testCustomOk() throws Exception { + assertEquals( + "No warnings.", + + lintProject( + "multiproject/library-manifest.xml=>AndroidManifest.xml", + + // Use a standard project properties instead: no warning since it's + // not a library project: + //"multiproject/library.properties=>project.properties", + + "res/layout/customview.xml" + )); + } + + public void testTypo() throws Exception { + assertEquals( + "wrong_namespace.xml:2: Warning: Unexpected namespace URI bound to the " + + "\"android\" prefix, was http://schemas.android.com/apk/res/andriod, " + + "expected http://schemas.android.com/apk/res/android", + + lintProject("res/layout/wrong_namespace.xml")); + } + + public void testTypo2() throws Exception { + assertEquals( + "wrong_namespace2.xml:2: Warning: URI is case sensitive: was " + + "\"http://schemas.android.com/apk/res/Android\", expected " + + "\"http://schemas.android.com/apk/res/android\"", + + lintProject("res/layout/wrong_namespace2.xml")); + } + + public void testTypo3() throws Exception { + assertEquals( + "wrong_namespace3.xml:2: Warning: Unexpected namespace URI bound to the " + + "\"android\" prefix, was http://schemas.android.com/apk/res/androi, " + + "expected http://schemas.android.com/apk/res/android", + + lintProject("res/layout/wrong_namespace3.xml")); + } + + public void testTypoOk() throws Exception { + assertEquals( + "No warnings.", + + lintProject("res/layout/wrong_namespace4.xml")); + } + + public void testUnused() throws Exception { + assertEquals( + "unused_namespace.xml:3: Warning: Unused namespace unused1\n" + + "unused_namespace.xml:4: Warning: Unused namespace unused2", + + lintProject("res/layout/unused_namespace.xml")); + } + + public void testUnusedOk() throws Exception { + assertEquals( + "No warnings.", + + lintProject("res/layout/layout1.xml")); + } +} diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/SecurityDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/SecurityDetectorTest.java index 1baed6c..925714e 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/SecurityDetectorTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/SecurityDetectorTest.java @@ -41,6 +41,15 @@ public class SecurityDetectorTest extends AbstractCheckTest { "res/values/strings.xml")); } + public void testBroken3() throws Exception { + // Not defining exported, but have intent-filters + assertEquals( + "AndroidManifest.xml:12: Warning: Exported service does not require permission", + lintProject( + "exportservice5.xml=>AndroidManifest.xml", + "res/values/strings.xml")); + } + public void testOk1() throws Exception { // Defines a permission on the <service> element assertEquals( diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ViewConstructorDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ViewConstructorDetectorTest.java index 3a6b9ec..dccb667 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ViewConstructorDetectorTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ViewConstructorDetectorTest.java @@ -46,4 +46,22 @@ public class ViewConstructorDetectorTest extends AbstractCheckTest { "bytecode/CustomView3.class.data=>bin/classes/test/bytecode/CustomView3.class" )); } + + public void testInheritLocal() throws Exception { + assertEquals( + "CustomViewTest.java: Warning: Custom view test/pkg/CustomViewTest is missing " + + "constructor used by tools: (Context) or (Context,AttributeSet) or " + + "(Context,AttributeSet,int)", + + lintProject( + "bytecode/.classpath=>.classpath", + "bytecode/AndroidManifest.xml=>AndroidManifest.xml", + "apicheck/Intermediate.java.txt=>src/test/pkg/Intermediate.java.txt", + "src/test/pkg/CustomViewTest.java.txt=>src/test/pkg/CustomViewTest.java", + "bytecode/CustomViewTest.class.data=>bin/classes/test/pkg/CustomViewTest.class", + "apicheck/Intermediate.class.data=>bin/classes/test/pkg/Intermediate.class", + "apicheck/Intermediate$IntermediateCustomV.class.data=>" + + "bin/classes/test/pkg/Intermediate$IntermediateCustomV.class" + )); + } } diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/WrongIdDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/WrongIdDetectorTest.java index 337ac5d..5783bd1 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/WrongIdDetectorTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/WrongIdDetectorTest.java @@ -48,4 +48,23 @@ public class WrongIdDetectorTest extends AbstractCheckTest { lintFiles("wrongid/layout1.xml=>res/layout/layout1.xml")); } + + public void testSuppressed() throws Exception { + assertEquals( + "No warnings.", + + lintProject( + "wrongid/ignorelayout1.xml=>res/layout/layout1.xml", + "wrongid/layout2.xml=>res/layout/layout2.xml", + "wrongid/ids.xml=>res/values/ids.xml" + )); + } + + public void testSuppressedSingleFile() throws Exception { + assertEquals( + "No warnings.", + + lintFiles("wrongid/ignorelayout1.xml=>res/layout/layout1.xml")); + } + } diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest3.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest3.class.data Binary files differnew file mode 100644 index 0000000..3d39e74 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest3.class.data diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest3.java.txt b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest3.java.txt new file mode 100644 index 0000000..d1ea3e4 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/ApiCallTest3.java.txt @@ -0,0 +1,12 @@ +package test.pkg; + +/** + * Call test where the parent class is some other project class which in turn + * extends the public API + */ +public class ApiCallTest3 extends Intermediate { + public void foo() { + // Virtual call + getActionBar(); // API 11 + } +} diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/Intermediate$IntermediateCustomV.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/Intermediate$IntermediateCustomV.class.data Binary files differnew file mode 100644 index 0000000..cbf323c --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/Intermediate$IntermediateCustomV.class.data diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/Intermediate.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/Intermediate.class.data Binary files differnew file mode 100644 index 0000000..8d187da --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/Intermediate.class.data diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/Intermediate.java.txt b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/Intermediate.java.txt new file mode 100644 index 0000000..fea5c4f --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/apicheck/Intermediate.java.txt @@ -0,0 +1,15 @@ +package test.pkg; + +import android.app.Activity; +import android.widget.Button; + +/** Local activity */ +public abstract class Intermediate extends Activity { + + /** Local Custom view */ + public abstract static class IntermediateCustomV extends Button { + public IntermediateCustomV() { + super(null); + } + } +} diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/CustomViewTest.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/CustomViewTest.class.data Binary files differnew file mode 100644 index 0000000..e145fbc --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/CustomViewTest.class.data diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/classpath-lib b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/classpath-lib new file mode 100644 index 0000000..e730df8 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/classpath-lib @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <classpathentry kind="src" path="gen"/> + <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> + <classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/> + <classpathentry kind="lib" path="libs/library.jar"/> + <classpathentry kind="output" path="bin/classes"/> +</classpath> diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/exportservice5.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/exportservice5.xml new file mode 100644 index 0000000..c9b9a78 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/exportservice5.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="foo.bar2" + android:versionCode="1" + android:versionName="1.0" > + + <uses-sdk android:minSdkVersion="14" /> + + <application + android:icon="@drawable/ic_launcher" + android:label="@string/app_name" > + <service + android:label="@string/app_name" + android:name="com.sample.service.serviceClass" + android:process=":remote" > + <intent-filter > + <action android:name="com.sample.service.serviceClass" > + </action> + </intent-filter> + </service> + </application> + +</manifest> + diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiplesdk.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiplesdk.xml new file mode 100644 index 0000000..950cf4d --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/multiplesdk.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="test.bytecode" + android:versionCode="1" + android:versionName="1.0" > + + <uses-sdk android:minSdkVersion="5" /> + <uses-sdk android:targetSdkVersion="14" /> + <uses-sdk android:maxSdkVersion="15" /> + + <application + android:icon="@drawable/ic_launcher" + android:label="@string/app_name" > + <activity + android:name=".BytecodeTestsActivity" + android:label="@string/app_name" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> + +</manifest> diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/customview.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/customview.xml new file mode 100644 index 0000000..976d636 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/customview.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:other="http://schemas.foo.bar.com/other" + xmlns:foo="http://schemas.android.com/apk/res/foo" + android:id="@+id/newlinear" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <foo.bar.Baz + android:id="@+id/button1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button1" + foo:misc="Custom attribute" + tools:ignore="HardcodedText" > + </foo.bar.Baz> + + <!-- Wrong namespace uri prefix: Don't warn --> + <foo.bar.Baz + android:id="@+id/button1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button1" + other:misc="Custom attribute" + tools:ignore="HardcodedText" > + </foo.bar.Baz> + +</LinearLayout> diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/unused_namespace.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/unused_namespace.xml new file mode 100644 index 0000000..f633e4b --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/unused_namespace.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<foo.bar.LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:unused1="http://schemas.android.com/apk/res/unused1" + xmlns:unused2="http://schemas.android.com/apk/res/unused1" + xmlns:unused3="http://foo.bar.com/foo" + xmlns:notunused="http://schemas.android.com/apk/res/notunused" + xmlns:tools="http://schemas.android.com/tools" > + + <foo.bar.Button + notunused:foo="Foo" + tools:ignore="HardcodedText" > + </foo.bar.Button> + +</foo.bar.LinearLayout> diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace.xml new file mode 100644 index 0000000..c6e2143 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/andriod" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <include + android:layout_width="wrap_content" + android:layout_height="wrap_content" + layout="@layout/layout2" /> + + <Button + android:id="@+id/button1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button" /> + + <Button + android:id="@+id/button2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button" /> + +</LinearLayout> diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace2.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace2.xml new file mode 100644 index 0000000..49dc611 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace2.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/Android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <include + android:layout_width="wrap_content" + android:layout_height="wrap_content" + layout="@layout/layout2" /> + + <Button + android:id="@+id/button1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button" /> + + <Button + android:id="@+id/button2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button" /> + +</LinearLayout> diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace3.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace3.xml new file mode 100644 index 0000000..02252b4 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace3.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:a="http://schemas.android.com/apk/res/androi" + a:layout_width="match_parent" + a:layout_height="match_parent" + a:orientation="vertical" > + + <include + a:layout_width="wrap_content" + a:layout_height="wrap_content" + layout="@layout/layout2" /> + + <Button + a:id="@+id/button1" + a:layout_width="wrap_content" + a:layout_height="wrap_content" + a:text="Button" /> + + <Button + a:id="@+id/button2" + a:layout_width="wrap_content" + a:layout_height="wrap_content" + a:text="Button" /> + +</LinearLayout> diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace4.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace4.xml new file mode 100644 index 0000000..4142622 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace4.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- This file does *not* have a wrong namespace: it's testdata to make sure we don't complain when "a" is defined for something unrelated --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:a="http://something/very/different" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <include + android:layout_width="wrap_content" + android:layout_height="wrap_content" + layout="@layout/layout2" /> + + <Button + android:id="@+id/button1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button" /> + + <Button + android:id="@+id/button2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button" /> + +</LinearLayout> diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/CustomViewTest.java.txt b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/CustomViewTest.java.txt new file mode 100644 index 0000000..1862ccc --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/CustomViewTest.java.txt @@ -0,0 +1,6 @@ +package test.pkg; + +import test.pkg.Intermediate.IntermediateCustomV; + +public class CustomViewTest extends IntermediateCustomV { +} diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/JavaPerformanceTest.java.txt b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/JavaPerformanceTest.java.txt new file mode 100644 index 0000000..5cd99d9 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/test/pkg/JavaPerformanceTest.java.txt @@ -0,0 +1,147 @@ +package test.pkg; + +import java.util.HashMap; +import java.util.Map; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.widget.Button; +/** Some test data for the JavaPerformanceDetector */ +@SuppressWarnings("unused") +public class JavaPerformanceTest extends Button { + public JavaPerformanceTest(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + private Rect cachedRect; + + @Override + protected void onDraw(android.graphics.Canvas canvas) { + super.onDraw(canvas); + + // Various allocations: + new String("foo"); + String s = new String("bar"); + + // This one should not be reported: + @SuppressLint("DrawAllocation") + Integer i = new Integer(5); + + // Cached object initialized lazily: should not complain about these + if (cachedRect == null) { + cachedRect = new Rect(0, 0, 100, 100); + } + if (cachedRect == null || cachedRect.width() != 50) { + cachedRect = new Rect(0, 0, 50, 100); + } + + boolean b = Boolean.valueOf(true); // auto-boxing + dummy(1, 2); + + // Non-allocations + super.animate(); + dummy2(1, 2); + int x = 4 + '5'; + + // This will involve allocations, but we don't track + // inter-procedural stuff here + someOtherMethod(); + } + + void dummy(Integer foo, int bar) { + dummy2(foo, bar); + } + + void dummy2(int foo, int bar) { + } + + void someOtherMethod() { + // Allocations are okay here + new String("foo"); + String s = new String("bar"); + boolean b = Boolean.valueOf(true); // auto-boxing + + // Sparse array candidates + Map<Integer, String> myMap = new HashMap<Integer, String>(); + // Should use SparseBooleanArray + Map<Integer, Boolean> myBoolMap = new HashMap<Integer, Boolean>(); + // Should use SparseIntArray + Map<Integer, Integer> myIntMap = new java.util.HashMap<Integer, Integer>(); + + // This one should not be reported: + @SuppressLint("UseSparseArrays") + Map<Integer, Object> myOtherMap = new HashMap<Integer, Object>(); + } + + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec, + boolean x) { // wrong signature + new String("not an error"); + } + + protected void onMeasure(int widthMeasureSpec) { // wrong signature + new String("not an error"); + } + + protected void onLayout(boolean changed, int left, int top, int right, + int bottom, int wrong) { // wrong signature + new String("not an error"); + } + + protected void onLayout(boolean changed, int left, int top, int right) { + // wrong signature + new String("not an error"); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, + int bottom) { + new String("flag me"); + } + + @SuppressWarnings("null") // not real code + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + new String("flag me"); + + // Forbidden factory methods: + Bitmap.createBitmap(100, 100, null); + android.graphics.Bitmap.createScaledBitmap(null, 100, 100, false); + BitmapFactory.decodeFile(null); + Canvas canvas = null; + canvas.getClipBounds(); // allocates on your behalf + canvas.getClipBounds(null); // NOT an error + + final int layoutWidth = getWidth(); + final int layoutHeight = getHeight(); + if (mAllowCrop && (mOverlay == null || mOverlay.getWidth() != layoutWidth || + mOverlay.getHeight() != layoutHeight)) { + mOverlay = Bitmap.createBitmap(layoutWidth, layoutHeight, Bitmap.Config.ARGB_8888); + mOverlayCanvas = new Canvas(mOverlay); + } + + if (widthMeasureSpec == 42) { + throw new IllegalStateException("Test"); // NOT an allocation + } + + // More lazy init tests + boolean initialized = false; + if (!initialized) { + new String("foo"); + initialized = true; + } + + // NOT lazy initialization + if (!initialized || mOverlay == null) { + new String("foo"); + } +} + + private boolean mAllowCrop; + private Canvas mOverlayCanvas; + private Bitmap mOverlay; +} diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/wrongid/ignorelayout1.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/wrongid/ignorelayout1.xml new file mode 100644 index 0000000..bc6d5fd --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/wrongid/ignorelayout1.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/RelativeLayout1" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" > + + <!-- my_id1 is defined in ids.xml, my_id2 is defined in main2, my_id3 does not exist --> + + <Button + android:id="@+id/button1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignBottom="@+id/button5" + android:layout_alignLeft="@+id/my_id2" + android:layout_alignParentTop="true" + android:layout_alignRight="@+id/my_id3" + android:layout_alignTop="@+id/my_id1" + android:text="Button" + tools:ignore="UnknownIdInLayout,UnknownId" /> + + <Button + android:id="@+id/button2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_below="@+id/button1" + android:text="Button" /> + + <Button + android:id="@+id/button3" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_below="@+id/button2" + android:text="Button" /> + + <Button + android:id="@+id/button4" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_below="@+id/button3" + android:text="Button" /> + +</RelativeLayout> diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogic.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogic.java index fde9b90..49395ef 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogic.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogic.java @@ -328,9 +328,11 @@ class PackagesDiffLogic { PkgItem item = itemIt.next(); if (mItemsToRemove.contains(item)) { itemIt.remove(); + hasChanged = true; } else if (item.hasUpdatePkg() && mUpdatesToRemove.containsKey(item.getUpdatePkg())) { item.removeUpdate(); + hasChanged = true; } } } @@ -471,6 +473,14 @@ class PackagesDiffLogic { return hasChanged; } + /** + * {@link PkgState}s to check in {@link #processSource(UpdateOp, SdkSource, Package[])}. + * The order matters. + * When installing the diff will have both the new and the installed item and we + * need to merge with the installed one before the new one. + */ + private final static PkgState[] PKG_STATES = { PkgState.INSTALLED, PkgState.NEW }; + /** Process all remote packages. Returns true if something changed. */ private boolean processSource(UpdateOp op, SdkSource source, Package[] packages) { boolean hasChanged = false; @@ -478,7 +488,7 @@ class PackagesDiffLogic { nextPkg: for (Package newPkg : packages) { for (PkgCategory cat : cats) { - for (PkgState state : PkgState.values()) { + for (PkgState state : PKG_STATES) { for (Iterator<PkgItem> currItemIt = cat.getItems().iterator(); currItemIt.hasNext(); ) { PkgItem currItem = currItemIt.next(); @@ -528,7 +538,7 @@ class PackagesDiffLogic { } break; case INSTALLED: - // if newPkg.revision <= mainPkg.revision: it's already installed, ignore. + // if newPkg.revision<=mainPkg.revision: it's already installed, ignore. if (newPkg.getRevision() > mainPkg.getRevision()) { // This is a new update for the main package. if (currItem.mergeUpdate(newPkg)) { diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PkgItem.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PkgItem.java index 2c8b2d2..e943819 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PkgItem.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/sdkman2/PkgItem.java @@ -40,8 +40,8 @@ public class PkgItem implements Comparable<PkgItem> { * a given remote package and the local repository. */ public enum PkgState { - // Implementation detail: order matters. Installed items must be dealt with before - // new items and the order of PkgState.values() matters. + // Implementation detail: if this is changed then PackageDiffLogic#STATES + // and PackageDiffLogic#processSource() need to be changed accordingly. /** * Package is locally installed and may or may not have an update. diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java index 3090884..8444f9f 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java @@ -25,6 +25,7 @@ import com.android.sdkuilib.ui.GridDialog; import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
@@ -160,8 +161,9 @@ public final class ProgressView implements IProgressUiProvider { // Process the app's event loop whilst we wait for the thread to finish
while (!mProgressBar.isDisposed() && t.isAlive()) {
- if (!mProgressBar.getDisplay().readAndDispatch()) {
- mProgressBar.getDisplay().sleep();
+ Display display = mProgressBar.getDisplay();
+ if (!mProgressBar.isDisposed() && !display.readAndDispatch()) {
+ display.sleep();
}
}
}
diff --git a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogicTest.java b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogicTest.java index c2b320d..eafe5ac 100755 --- a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogicTest.java +++ b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/sdkman2/PackagesDiffLogicTest.java @@ -240,7 +240,7 @@ public class PackagesDiffLogicTest extends TestCase { new MockEmptyPackage(src1, "type1", 1) })); - assertFalse(m.updateEnd(true /*sortByApi*/)); + assertTrue(m.updateEnd(true /*sortByApi*/)); assertEquals( "PkgCategoryApi <API=TOOLS, label=Tools, #items=0>\n" + |