diff options
28 files changed, 661 insertions, 25 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ComplementingConfiguration.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ComplementingConfiguration.java index 66a29c8..7ff0354 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ComplementingConfiguration.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ComplementingConfiguration.java @@ -17,7 +17,9 @@ package com.android.ide.eclipse.adt.internal.editors.layout.configuration; import com.android.annotations.NonNull; import com.android.annotations.Nullable; +import com.android.ide.common.rendering.api.Capability; import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; +import com.android.resources.Density; import com.android.resources.NightMode; import com.android.resources.UiMode; import com.android.sdklib.IAndroidTarget; @@ -100,6 +102,9 @@ public class ComplementingConfiguration extends NestedConfiguration { @NonNull Configuration parent) { ComplementingConfiguration configuration = new ComplementingConfiguration(other.mConfigChooser, parent); + configuration.setDisplayName(other.getDisplayName()); + configuration.setActivity(other.getActivity()); + configuration.mUpdateDisplayName = other.mUpdateDisplayName; configuration.mOverrideLocale = other.mOverrideLocale; configuration.mOverrideTarget = other.mOverrideTarget; configuration.mOverrideDevice = other.mOverrideDevice; @@ -279,9 +284,17 @@ public class ComplementingConfiguration extends NestedConfiguration { to = biggest + 0.1; } + boolean canScaleNinePatch = supports(Capability.FIXED_SCALABLE_NINE_PATCH); for (Device d : devices) { double size = getScreenSize(d); if (size >= from && size < to) { + if (!canScaleNinePatch) { + Density density = getDensity(d); + if (density == Density.TV || density == Density.LOW) { + continue; + } + } + device = d; break; } @@ -294,6 +307,26 @@ public class ComplementingConfiguration extends NestedConfiguration { return device; } + + /** + * Returns the density of the given device + * + * @param device the device to check + * @return the density or null + */ + @Nullable + public static Density getDensity(@NonNull Device device) { + Hardware hardware = device.getDefaultHardware(); + if (hardware != null) { + Screen screen = hardware.getScreen(); + if (screen != null) { + return screen.getPixelDensity(); + } + } + + return null; + } + private static double getScreenSize(@NonNull Device device) { Hardware hardware = device.getDefaultHardware(); if (hardware != null) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java index 1615722..c0b57ac 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java @@ -23,6 +23,7 @@ import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.ide.common.api.Rect; +import com.android.ide.common.rendering.api.Capability; import com.android.ide.common.resources.ResourceRepository; import com.android.ide.common.resources.configuration.DensityQualifier; import com.android.ide.common.resources.configuration.DeviceConfigHelper; @@ -36,6 +37,7 @@ import com.android.ide.common.resources.configuration.ScreenSizeQualifier; import com.android.ide.common.resources.configuration.UiModeQualifier; import com.android.ide.common.resources.configuration.VersionQualifier; import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderService; import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; import com.android.ide.eclipse.adt.internal.resources.ResourceHelper; import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; @@ -197,6 +199,7 @@ public class Configuration { copy.mLocale = original.getLocale(); copy.mUiMode = original.getUiMode(); copy.mNightMode = original.getNightMode(); + copy.mDisplayName = original.getDisplayName(); return copy; } @@ -1062,6 +1065,22 @@ public class Configuration { return null; } + /** + * Returns true if this configuration supports the given rendering + * capability + * + * @param capability the capability to check + * @return true if the capability is supported + */ + public boolean supports(Capability capability) { + IAndroidTarget target = getTarget(); + if (target != null) { + return RenderService.supports(target, capability); + } + + return false; + } + @Override public String toString() { return Objects.toStringHelper(this.getClass()) diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/NestedConfiguration.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/NestedConfiguration.java index 432abdb..7c2559c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/NestedConfiguration.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/NestedConfiguration.java @@ -83,6 +83,9 @@ public class NestedConfiguration extends Configuration { @NonNull Configuration parent) { NestedConfiguration configuration = new NestedConfiguration(other.mConfigChooser, parent); + configuration.setDisplayName(values.getDisplayName()); + configuration.setActivity(values.getActivity()); + configuration.mOverrideLocale = other.mOverrideLocale; if (configuration.mOverrideLocale) { configuration.setLocale(values.getLocale(), true); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java index 93fdb5e..9b8eeea 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java @@ -2048,6 +2048,21 @@ public class GraphicalEditorPart extends EditorPart "or fix the theme style references.\n\n"); } + List<Throwable> trace = logger.getFirstTrace(); + if (trace != null + && trace.toString().contains( + "java.lang.IndexOutOfBoundsException: Index: 2, Size: 2") //$NON-NLS-1$ + && mConfigChooser.getConfiguration().getDensity() == Density.TV) { + addBoldText(mErrorLabel, + "It looks like you are using a render target where the layout library " + + "does not support the tvdpi density.\n\n"); + addText(mErrorLabel, "Please try either updating to " + + "the latest available version (using the SDK manager), or if no updated " + + "version is available for this specific version of Android, try using " + + "a more recent render target version.\n\n"); + + } + if (hasAaptErrors && logger.seenTagPrefix(LayoutLog.TAG_RESOURCES_PREFIX)) { // Text will automatically be wrapped by the error widget so no reason // to insert linebreaks in this error message: diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewManager.java index 80391cd..f90e9ac 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewManager.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewManager.java @@ -23,6 +23,7 @@ import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPre import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.ide.common.api.Rect; +import com.android.ide.common.rendering.api.Capability; import com.android.ide.common.resources.configuration.DensityQualifier; import com.android.ide.common.resources.configuration.DeviceConfigHelper; import com.android.ide.common.resources.configuration.FolderConfiguration; @@ -894,6 +895,7 @@ public class RenderPreviewManager { ConfigurationChooser chooser = getChooser(); List<Device> devices = chooser.getDeviceList(); Configuration configuration = chooser.getConfiguration(); + boolean canScaleNinePatch = configuration.supports(Capability.FIXED_SCALABLE_NINE_PATCH); // Rearrange the devices a bit such that the most interesting devices bubble // to the front @@ -935,7 +937,11 @@ public class RenderPreviewManager { DensityQualifier density = c.getDensityQualifier(); if (density != null) { Density d = density.getValue(); - if (Density.LOW.equals(d)) { + if (d == Density.LOW) { + interesting = false; + } + + if (!canScaleNinePatch && d == Density.TV) { interesting = false; } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java index ccf4068..2a1f3b7 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java @@ -17,10 +17,12 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; import static com.android.SdkConstants.LAYOUT_RESOURCE_PREFIX; +import com.android.annotations.NonNull; import com.android.ide.common.api.IClientRulesEngine; import com.android.ide.common.api.INode; import com.android.ide.common.api.Rect; import com.android.ide.common.rendering.LayoutLibrary; +import com.android.ide.common.rendering.api.Capability; import com.android.ide.common.rendering.api.DrawableParams; import com.android.ide.common.rendering.api.IImageFactory; import com.android.ide.common.rendering.api.ILayoutPullParser; @@ -49,7 +51,13 @@ import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElement import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.resources.Density; +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.devices.ButtonType; +import com.android.sdklib.devices.Device; +import com.android.sdklib.devices.Hardware; import org.eclipse.core.resources.IProject; import org.xmlpull.v1.XmlPullParser; @@ -81,6 +89,7 @@ public class RenderService { private final ResourceResolver mResourceResolver; private final int mMinSdkVersion; private final int mTargetSdkVersion; + private final boolean mSoftwareButtons; private final LayoutLibrary mLayoutLib; private final IImageFactory mImageFactory; private final Density mDensity; @@ -120,6 +129,7 @@ public class RenderService { mProjectCallback = editor.getProjectCallback(true /*reset*/, mLayoutLib); mMinSdkVersion = editor.getMinSdkVersion(); mTargetSdkVersion = editor.getTargetSdkVersion(); + mSoftwareButtons = getButtonType(config) == ButtonType.SOFT; } private RenderService(GraphicalEditorPart editor, FolderConfiguration configuration, @@ -137,6 +147,7 @@ public class RenderService { mProjectCallback = editor.getProjectCallback(true /*reset*/, mLayoutLib); mMinSdkVersion = editor.getMinSdkVersion(); mTargetSdkVersion = editor.getTargetSdkVersion(); + mSoftwareButtons = getButtonType(config) == ButtonType.SOFT; // TODO: Look up device etc and offer additional configuration options here? Density density = Density.MEDIUM; @@ -152,6 +163,44 @@ public class RenderService { mScreenSize = configuration.getScreenSizeQualifier(); } + @NonNull + private static ButtonType getButtonType(@NonNull Configuration configuration) { + Device device = configuration.getDevice(); + if (device != null) { + Hardware hardware = device.getDefaultHardware(); + if (hardware != null) { + return hardware.getButtonType(); + } + } + + return ButtonType.SOFT; + } + + /** + * Returns true if this configuration supports the given rendering + * capability + * + * @param target the target to look up the layout library for + * @param capability the capability to check + * @return true if the capability is supported + */ + public static boolean supports( + @NonNull IAndroidTarget target, + @NonNull Capability capability) { + Sdk sdk = Sdk.getCurrent(); + if (sdk != null) { + AndroidTargetData targetData = sdk.getTargetData(target); + if (targetData != null) { + LayoutLibrary layoutLib = targetData.getLayoutLibrary(); + if (layoutLib != null) { + return layoutLib.supports(capability); + } + } + } + + return false; + } + /** * Sets the screen size and density to use for rendering * @@ -398,6 +447,7 @@ public class RenderService { mProjectCallback, mMinSdkVersion, mTargetSdkVersion, + mSoftwareButtons, mLogger); // Request margin and baseline information. @@ -533,6 +583,7 @@ public class RenderService { mProjectCallback, mMinSdkVersion, mTargetSdkVersion, + mSoftwareButtons, mLogger); params.setLayoutOnly(); params.setForceNoDecor(); diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/layoutRendering/ApiDemosRenderingTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/layoutRendering/ApiDemosRenderingTest.java index 30f23de..7a6eef4 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/layoutRendering/ApiDemosRenderingTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/layoutRendering/ApiDemosRenderingTest.java @@ -263,6 +263,7 @@ public class ApiDemosRenderingTest extends SdkTestCase { projectCallBack, 1, // minSdkVersion 1, // targetSdkVersion + false, // softwareButtons null //logger )); diff --git a/layoutlib_api/src/com/android/ide/common/rendering/api/Bridge.java b/layoutlib_api/src/com/android/ide/common/rendering/api/Bridge.java index 06a01d5..a19b8d5 100644 --- a/layoutlib_api/src/com/android/ide/common/rendering/api/Bridge.java +++ b/layoutlib_api/src/com/android/ide/common/rendering/api/Bridge.java @@ -32,7 +32,7 @@ import java.util.Map; */ public abstract class Bridge { - public final static int API_CURRENT = 8; + public final static int API_CURRENT = 9; /** * Returns the API level of the layout library. diff --git a/layoutlib_api/src/com/android/ide/common/rendering/api/Capability.java b/layoutlib_api/src/com/android/ide/common/rendering/api/Capability.java index a7ab7ae..5ad438d 100644 --- a/layoutlib_api/src/com/android/ide/common/rendering/api/Capability.java +++ b/layoutlib_api/src/com/android/ide/common/rendering/api/Capability.java @@ -61,5 +61,9 @@ public enum Capability { */ FULL_ANIMATED_VIEW_MANIPULATION, ADAPTER_BINDING, - EXTENDED_VIEWINFO; + EXTENDED_VIEWINFO, + /** + * Ability to properly resize nine-patch assets. + */ + FIXED_SCALABLE_NINE_PATCH; } diff --git a/layoutlib_api/src/com/android/ide/common/rendering/api/SessionParams.java b/layoutlib_api/src/com/android/ide/common/rendering/api/SessionParams.java index a620b05..e3edbd2 100644 --- a/layoutlib_api/src/com/android/ide/common/rendering/api/SessionParams.java +++ b/layoutlib_api/src/com/android/ide/common/rendering/api/SessionParams.java @@ -52,6 +52,7 @@ public class SessionParams extends RenderParams { private final ILayoutPullParser mLayoutDescription; private final RenderingMode mRenderingMode; + private final boolean mSoftwareButtons; private boolean mLayoutOnly = false; private Map<ResourceReference, AdapterBinding> mAdapterBindingMap; private boolean mExtendedViewInfoMode = false; @@ -72,6 +73,7 @@ public class SessionParams extends RenderParams { * the project. * @param minSdkVersion the minSdkVersion of the project * @param targetSdkVersion the targetSdkVersion of the project + * @param softwareButtons whether the device use software buttons * @param log the object responsible for displaying warning/errors to the user. */ public SessionParams( @@ -83,18 +85,21 @@ public class SessionParams extends RenderParams { RenderResources renderResources, IProjectCallback projectCallback, int minSdkVersion, int targetSdkVersion, + boolean softwareButtons, LayoutLog log) { super(projectKey, screenWidth, screenHeight, density, xdpi, ydpi, renderResources, projectCallback, minSdkVersion, targetSdkVersion, log); mLayoutDescription = layoutDescription; mRenderingMode = renderingMode; + mSoftwareButtons = softwareButtons; } public SessionParams(SessionParams params) { super(params); mLayoutDescription = params.mLayoutDescription; mRenderingMode = params.mRenderingMode; + mSoftwareButtons = params.mSoftwareButtons; if (params.mAdapterBindingMap != null) { mAdapterBindingMap = new HashMap<ResourceReference, AdapterBinding>( params.mAdapterBindingMap); @@ -110,6 +115,10 @@ public class SessionParams extends RenderParams { return mRenderingMode; } + public boolean hasSoftwareButtons() { + return mSoftwareButtons; + } + public void setLayoutOnly() { mLayoutOnly = true; } 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 2e21c06..2a68ddf 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 @@ -20,6 +20,7 @@ import static com.android.SdkConstants.CLASS_FOLDER; import static com.android.SdkConstants.DOT_JAR; import static com.android.SdkConstants.GEN_FOLDER; import static com.android.SdkConstants.LIBS_FOLDER; +import static com.android.SdkConstants.RES_FOLDER; import static com.android.SdkConstants.SRC_FOLDER; import com.android.SdkConstants; @@ -233,6 +234,23 @@ public abstract class LintClient { } /** + * Returns the resource folder. + * + * @param project the project to look up the resource folder for + * @return a file pointing to the resource folder, or null if the project + * does not contain any resources + */ + @Nullable + public File getResourceFolder(@NonNull Project project) { + File res = new File(project.getDir(), RES_FOLDER); + if (res.exists()) { + return res; + } + + return null; + } + + /** * 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 aad1ad7..7f6c0ee 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 @@ -523,6 +523,13 @@ public class LintDriver { assert detector instanceof Detector.ClassScanner : detector; } } + + List<Detector> classCodeDetectors = mScopeDetectors.get(Scope.ALL_CLASS_FILES); + if (classCodeDetectors != null) { + for (Detector detector : classCodeDetectors) { + assert detector instanceof Detector.ClassScanner : detector; + } + } } } @@ -819,8 +826,8 @@ public class LintDriver { if (files != null) { checkIndividualResources(project, main, xmlDetectors, files); } else { - File res = new File(project.getDir(), RES_FOLDER); - if (res.exists() && xmlDetectors.size() > 0) { + File res = project.getResourceFolder(); + if (res != null && xmlDetectors.size() > 0) { checkResFolder(project, main, res, xmlDetectors); } } @@ -850,7 +857,9 @@ public class LintDriver { return; } - if (mScope.contains(Scope.CLASS_FILE) || mScope.contains(Scope.JAVA_LIBRARIES)) { + if (mScope.contains(Scope.CLASS_FILE) + || mScope.contains(Scope.ALL_CLASS_FILES) + || mScope.contains(Scope.JAVA_LIBRARIES)) { checkClasses(project, main); } @@ -1054,6 +1063,7 @@ public class LintDriver { } runClassDetectors(Scope.CLASS_FILE, classEntries, project, main); + runClassDetectors(Scope.ALL_CLASS_FILES, classEntries, project, main); } private void checkIndividualClassFiles( @@ -1664,6 +1674,12 @@ public class LintDriver { @Override @Nullable + public File getResourceFolder(@NonNull Project project) { + return mDelegate.getResourceFolder(project); + } + + @Override + @Nullable public IDomParser getDomParser() { return mDelegate.getDomParser(); } diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java index 60c9e97..c0d5a85 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java @@ -668,6 +668,9 @@ public class LintUtils { return false; } } + } else if (Project.isAospFrameworksProject(dir)) { + // Hardcoded AOSP support for the frameworks project + return true; } return hasManifest; 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 b584020..eb41807 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 @@ -25,9 +25,11 @@ import static com.android.SdkConstants.ATTR_PACKAGE; import static com.android.SdkConstants.ATTR_TARGET_SDK_VERSION; import static com.android.SdkConstants.PROGUARD_CONFIG; import static com.android.SdkConstants.PROJECT_PROPERTIES; +import static com.android.SdkConstants.RES_FOLDER; import static com.android.SdkConstants.TAG_USES_SDK; import static com.android.SdkConstants.VALUE_TRUE; +import com.android.SdkConstants; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.tools.lint.client.api.Configuration; @@ -247,6 +249,9 @@ public class Project { @NonNull public List<File> getJavaSourceFolders() { if (mJavaSourceFolders == null) { + if (isAospFrameworksProject(mDir)) { + return Collections.singletonList(new File(mDir, "java")); //$NON-NLS-1$ + } if (isAospBuildEnvironment()) { String top = getAospTop(); if (mDir.getAbsolutePath().startsWith(top)) { @@ -268,6 +273,21 @@ public class Project { @NonNull public List<File> getJavaClassFolders() { if (mJavaClassFolders == null) { + if (isAospFrameworksProject(mDir)) { + File top = mDir.getParentFile().getParentFile().getParentFile(); + if (top != null) { + File out = new File(top, "out"); + if (out.exists()) { + String relative = + "target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar"; + File jar = new File(out, relative.replace('/', File.separatorChar)); + if (jar.exists()) { + mJavaClassFolders = Collections.singletonList(jar); + return mJavaClassFolders; + } + } + } + } if (isAospBuildEnvironment()) { String top = getAospTop(); if (mDir.getAbsolutePath().startsWith(top)) { @@ -304,6 +324,28 @@ public class Project { } /** + * Returns the resource folder. + * + * @return a file pointing to the resource folder, or null if the project + * does not contain any resources + */ + @Nullable + public File getResourceFolder() { + File folder = mClient.getResourceFolder(this); + + if (folder != null && isAospFrameworksProject(mDir)) { + // No manifest file for this project: just init the manifest values here + mMinSdk = mTargetSdk = SdkConstants.HIGHEST_KNOWN_API; + folder = new File(folder, RES_FOLDER); + if (!folder.exists()) { + folder = null; + } + } + + return folder; + } + + /** * 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 @@ -643,6 +685,31 @@ public class Project { return sAospBuild.booleanValue(); } + /** + * Is this the frameworks AOSP project? Needs some hardcoded support since + * it doesn't have a manifest file, etc. + * + * @param dir the project directory to check + * @return true if this looks like the frameworks/base/core project + */ + static boolean isAospFrameworksProject(@NonNull File dir) { + if (!dir.getPath().endsWith("core")) { //$NON-NLS-1$ + return false; + } + + File parent = dir.getParentFile(); + if (parent == null || !parent.getName().equals("base")) { //$NON-NLS-1$ + return false; + } + + parent = parent.getParentFile(); + if (parent == null || !parent.getName().equals("frameworks")) { //$NON-NLS-1$ + return false; + } + + return true; + } + /** Get the root AOSP dir, if any */ private static String getAospTop() { return System.getenv("ANDROID_BUILD_TOP"); //$NON-NLS-1$ 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 a917c11..fcafb64 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 @@ -70,6 +70,13 @@ public enum Scope { */ CLASS_FILE, + /** + * The analysis considers <b>all</b> the Java class files together. + * <p> + * This flag is mutually exclusive with {@link #CLASS_FILE}. + */ + ALL_CLASS_FILES, + /** The analysis considers the manifest file */ MANIFEST, 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 d1c8f67..aafcd5c 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 @@ -55,7 +55,7 @@ public class BuiltinIssueRegistry extends IssueRegistry { private static final List<Issue> sIssues; static { - final int initialCapacity = 119; + final int initialCapacity = 120; List<Issue> issues = new ArrayList<Issue>(initialCapacity); issues.add(AccessibilityDetector.ISSUE); @@ -86,6 +86,7 @@ public class BuiltinIssueRegistry extends IssueRegistry { issues.add(TooManyViewsDetector.TOO_MANY); issues.add(TooManyViewsDetector.TOO_DEEP); issues.add(GridLayoutDetector.ISSUE); + issues.add(OverrideDetector.ISSUE); issues.add(OnClickDetector.ISSUE); issues.add(ViewTagDetector.ISSUE); issues.add(LocaleDetector.STRING_LOCALE); 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 2f416b5..9d9cf14 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 @@ -32,7 +32,6 @@ import static com.android.SdkConstants.DRAWABLE_MDPI; import static com.android.SdkConstants.DRAWABLE_PREFIX; import static com.android.SdkConstants.DRAWABLE_XHDPI; import static com.android.SdkConstants.MENU_TYPE; -import static com.android.SdkConstants.RES_FOLDER; import static com.android.SdkConstants.R_CLASS; import static com.android.SdkConstants.R_DRAWABLE_PREFIX; import static com.android.SdkConstants.TAG_ACTIVITY; @@ -53,6 +52,7 @@ import com.android.tools.lint.detector.api.Issue; import com.android.tools.lint.detector.api.JavaContext; import com.android.tools.lint.detector.api.LintUtils; import com.android.tools.lint.detector.api.Location; +import com.android.tools.lint.detector.api.Project; import com.android.tools.lint.detector.api.ResourceXmlDetector; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; @@ -335,17 +335,17 @@ public class IconDetector extends ResourceXmlDetector implements Detector.JavaSc @Override public void afterCheckLibraryProject(@NonNull Context context) { - checkResourceFolder(context, context.getProject().getDir()); + checkResourceFolder(context, context.getProject()); } @Override public void afterCheckProject(@NonNull Context context) { - checkResourceFolder(context, context.getProject().getDir()); + checkResourceFolder(context, context.getProject()); } - private void checkResourceFolder(Context context, File dir) { - File res = new File(dir, RES_FOLDER); - if (res.isDirectory()) { + private void checkResourceFolder(Context context, @NonNull Project project) { + File res = project.getResourceFolder(); + if (res != null) { File[] folders = res.listFiles(); if (folders != null) { boolean checkFolders = context.isEnabled(ICON_DENSITIES) diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/OverrideDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/OverrideDetector.java new file mode 100644 index 0000000..137bc21 --- /dev/null +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/OverrideDetector.java @@ -0,0 +1,271 @@ +/* + * 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.SdkConstants.CONSTRUCTOR_NAME; +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; +import static org.objectweb.asm.Opcodes.ACC_PROTECTED; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static org.objectweb.asm.Opcodes.ACC_STATIC; + +import com.android.annotations.NonNull; +import com.android.tools.lint.client.api.LintDriver; +import com.android.tools.lint.detector.api.Category; +import com.android.tools.lint.detector.api.ClassContext; +import com.android.tools.lint.detector.api.Context; +import com.android.tools.lint.detector.api.Detector; +import com.android.tools.lint.detector.api.Detector.ClassScanner; +import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.Location; +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.Maps; +import com.google.common.collect.Sets; +import com.google.common.collect.Sets.SetView; + +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * Checks for accidental overrides + */ +public class OverrideDetector extends Detector implements ClassScanner { + /** Accidental overrides */ + public static final Issue ISSUE = Issue.create( + "DalvikOverride", //$NON-NLS-1$ + "Looks for methods treated as overrides by Dalvik", + + "The Android virtual machine will treat a package private method in one " + + "class as overriding a package private method in its super class, even if " + + "they are in separate packages. This may be surprising, but for compatibility " + + "reasons the behavior has not been changed (yet).\n" + + "\n" + + "If you really did intend for this method to override the other, make the " + + "method `protected` instead.\n" + + "\n" + + "If you did *not* intend the override, consider making the method private, or " + + "changing its name or signature.", + + Category.CORRECTNESS, + 7, + Severity.ERROR, + OverrideDetector.class, + EnumSet.of(Scope.ALL_CLASS_FILES)); + + /** map from owner class name to JVM signatures for its package private methods */ + private Map<String, Set<String>> mPackagePrivateMethods = Maps.newHashMap(); + + /** Map from owner to signature to super class being overridden */ + private Map<String, Map<String, String>> mErrors; + + /** + * Map from owner to signature to corresponding location. When there are + * errors a single error can have locations for both the overriding and + * overridden methods. + */ + private Map<String, Map<String, Location>> mLocations; + + /** Constructs a new {@link OverrideDetector} */ + public OverrideDetector() { + } + + @Override + public @NonNull Speed getSpeed() { + return Speed.NORMAL; + } + + @Override + public void afterCheckProject(@NonNull Context context) { + // Process the check in two passes: + // + // In the first pass, gather the full set of package private methods for + // each class. + // When all classes have been processed at the end of the first pass, + // find out whether any of the methods are potentially overriding those + // in its super classes. + // + // If so, request a second pass. In the second pass, we gather full locations + // for both the base and overridden method calls, and store these. + // If the location is found to be in a suppressed context, remove that error + // entry. + // + // At the end of the second pass, we generate the errors, combining locations + // from both the overridden and overriding methods. + if (context.getPhase() == 1) { + Set<String> classes = mPackagePrivateMethods.keySet(); + LintDriver driver = context.getDriver(); + for (String owner : classes) { + Set<String> methods = mPackagePrivateMethods.get(owner); + String superClass = driver.getSuperClass(owner); + int packageIndex = owner.lastIndexOf('/'); + while (superClass != null) { + int superPackageIndex = superClass.lastIndexOf('/'); + + // Only compare methods that differ in packages + if (packageIndex == -1 || superPackageIndex != packageIndex || + !owner.regionMatches(0, superClass, 0, packageIndex)) { + Set<String> superMethods = mPackagePrivateMethods.get(superClass); + if (superMethods != null) { + SetView<String> intersection = Sets.intersection(methods, + superMethods); + if (!intersection.isEmpty()) { + if (mLocations == null) { + mLocations = Maps.newHashMap(); + } + // We need a separate data structure to keep track of which + // signatures are in error, + if (mErrors == null) { + mErrors = Maps.newHashMap(); + } + + for (String signature : intersection) { + Map<String, Location> locations = mLocations.get(owner); + if (locations == null) { + locations = Maps.newHashMap(); + mLocations.put(owner, locations); + } + locations.put(signature, null); + + locations = mLocations.get(superClass); + if (locations == null) { + locations = Maps.newHashMap(); + mLocations.put(superClass, locations); + } + locations.put(signature, null); + + + Map<String, String> errors = mErrors.get(owner); + if (errors == null) { + errors = Maps.newHashMap(); + mErrors.put(owner, errors); + } + errors.put(signature, superClass); + } + } + } + } + superClass = driver.getSuperClass(superClass); + } + } + + if (mErrors != null) { + context.requestRepeat(this, ISSUE.getScope()); + } + } else { + assert context.getPhase() == 2; + + for (Entry<String, Map<String, String>> ownerEntry : mErrors.entrySet()) { + String owner = ownerEntry.getKey(); + Map<String, String> methodToSuper = ownerEntry.getValue(); + for (Entry<String, String> entry : methodToSuper.entrySet()) { + String signature = entry.getKey(); + String superClass = entry.getValue(); + + Map<String, Location> ownerLocations = mLocations.get(owner); + if (ownerLocations != null) { + Location location = ownerLocations.get(signature); + if (location != null) { + Map<String, Location> superLocations = mLocations.get(superClass); + if (superLocations != null) { + Location superLocation = superLocations.get(signature); + if (superLocation != null) { + location.setSecondary(superLocation); + superLocation.setMessage( + "This method is treated as overridden"); + } + } + String methodName = signature; + int index = methodName.indexOf('('); + if (index != -1) { + methodName = methodName.substring(0, index); + } + String message = String.format( + "This package private method may be unintentionally " + + "overriding %1$s in %2$s", methodName, + ClassContext.getFqcn(superClass)); + context.report(ISSUE, location, message, null); + } + } + } + } + } + } + + @SuppressWarnings("rawtypes") // ASM4 API + @Override + public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) { + List methodList = classNode.methods; + if (context.getPhase() == 1) { + for (Object m : methodList) { + MethodNode method = (MethodNode) m; + int access = method.access; + // Only record non-static package private methods + if ((access & (ACC_STATIC|ACC_PRIVATE|ACC_PROTECTED|ACC_PUBLIC)) != 0) { + continue; + } + + // Ignore constructors too + if (CONSTRUCTOR_NAME.equals(method.name)) { + continue; + } + + String owner = classNode.name; + Set<String> methods = mPackagePrivateMethods.get(owner); + if (methods == null) { + methods = Sets.newHashSetWithExpectedSize(methodList.size()); + mPackagePrivateMethods.put(owner, methods); + } + methods.add(method.name + method.desc); + } + } else { + assert context.getPhase() == 2; + Map<String, Location> methods = mLocations.get(classNode.name); + if (methods == null) { + // No locations needed from this class + return; + } + + for (Object m : methodList) { + MethodNode method = (MethodNode) m; + + String signature = method.name + method.desc; + if (methods.containsKey(signature)){ + if (method != null && context.getDriver().isSuppressed(ISSUE, method)) { + Map<String, String> errors = mErrors.get(classNode.name); + if (errors != null) { + errors.remove(signature); + } + continue; + } + + Location location = context.getLocation(method, classNode); + methods.put(signature, location); + String description = ClassContext.createSignature(classNode.name, + method.name, method.desc); + location.setClientData(description); + } + } + } + } +} diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/RequiredAttributeDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/RequiredAttributeDetector.java index 81b0626..a71f4b4 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/RequiredAttributeDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/RequiredAttributeDetector.java @@ -319,7 +319,15 @@ public class RequiredAttributeDetector extends LayoutDetector implements Detecto String parentTag = element.getParentNode() != null ? element.getParentNode().getNodeName() : ""; - if (TABLE_LAYOUT.equals(parentTag) || TABLE_ROW.equals(parentTag)) { + if (TABLE_LAYOUT.equals(parentTag) + || TABLE_ROW.equals(parentTag) + || GRID_LAYOUT.equals(parentTag) + || FQCN_GRID_LAYOUT_V7.equals(parentTag)) { + return; + } + + String tag = element.getTagName(); + if (tag.equals(VIEW_INCLUDE)) { return; } @@ -368,11 +376,6 @@ public class RequiredAttributeDetector extends LayoutDetector implements Detecto } } - String tag = element.getTagName(); - if (tag.equals(VIEW_INCLUDE) || tag.equals(GRID_LAYOUT) - || tag.equals(FQCN_GRID_LAYOUT_V7)) { - return; - } String message; if (!(hasWidth || hasHeight)) { if (certain) { diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/UnusedResourceDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/UnusedResourceDetector.java index 1b4163e..5a3f174 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/UnusedResourceDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/UnusedResourceDetector.java @@ -26,7 +26,6 @@ import static com.android.SdkConstants.DOT_XML; import static com.android.SdkConstants.RESOURCE_CLR_STYLEABLE; import static com.android.SdkConstants.RESOURCE_CLZ_ARRAY; import static com.android.SdkConstants.RESOURCE_CLZ_ID; -import static com.android.SdkConstants.RES_FOLDER; import static com.android.SdkConstants.R_ATTR_PREFIX; import static com.android.SdkConstants.R_CLASS; import static com.android.SdkConstants.R_ID_PREFIX; @@ -261,8 +260,11 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec if (type != null && LintUtils.isFileBasedResourceType(type)) { String name = resource.substring(secondDot + 1); - File res = new File(context.getProject().getDir(), RES_FOLDER); - File[] folders = res.listFiles(); + File[] folders = null; + File res = context.getProject().getResourceFolder(); + if (res != null) { + folders = res.listFiles(); + } if (folders != null) { // Process folders in alphabetical order such that we process // based folders first: we want the locations in base folder diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/OnClickDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/OnClickDetectorTest.java index e9f70d4..e4d8bb5 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/OnClickDetectorTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/OnClickDetectorTest.java @@ -54,8 +54,7 @@ public class OnClickDetectorTest extends AbstractCheckTest { "res/layout/onclick.xml:58: Error: Corresponding method handler 'public void simple_typo(android.view.View)' not found (did you mean void test.pkg.OnClickActivity#simple_tyop(android.view.View) ?) [OnClick]\n" + " android:onClick=\"simple_typo\"\n" + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + - "9 errors, 0 warnings\n" + - "", + "9 errors, 0 warnings\n", lintProject( "bytecode/.classpath=>.classpath", @@ -73,5 +72,4 @@ public class OnClickDetectorTest extends AbstractCheckTest { lintProject("res/layout/accessibility.xml")); } - } diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/OverrideDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/OverrideDetectorTest.java new file mode 100644 index 0000000..68f80f9 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/OverrideDetectorTest.java @@ -0,0 +1,47 @@ +/* + * 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 OverrideDetectorTest extends AbstractCheckTest { + @Override + protected Detector getDetector() { + return new OverrideDetector(); + } + + public void test() throws Exception { + assertEquals( + "src/pkg2/Class2.java:7: Error: This package private method may be unintentionally overriding method in pkg1.Class1 [DalvikOverride]\n" + + " void method() { // Flag this as an accidental override\n" + + " ~~~~~~\n" + + " src/pkg1/Class1.java:4: This method is treated as overridden\n" + + "1 errors, 0 warnings\n", + + lintProject( + "bytecode/.classpath=>.classpath", + "bytecode/AndroidManifest.xml=>AndroidManifest.xml", + "src/pkg1/Class1.java.txt=>src/pkg1/Class1.java", + "src/pkg2/Class2.java.txt=>src/pkg2/Class2.java", + "bytecode/Class1.class.data=>bin/classes/pkg1/Class1.class", + "bytecode/Class1$Class4.class.data=>bin/classes/pkg1/Class1$Class4.class", + "bytecode/Class2.class.data=>bin/classes/pkg2/Class2.class", + "bytecode/Class2$Class3.class.data=>bin/classes/pkg2/Class2$Class3.class" + )); + } +} diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/Class1$Class4.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/Class1$Class4.class.data Binary files differnew file mode 100644 index 0000000..bfd88db --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/Class1$Class4.class.data diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/Class1.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/Class1.class.data Binary files differnew file mode 100644 index 0000000..cee3058 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/Class1.class.data diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/Class2$Class3.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/Class2$Class3.class.data Binary files differnew file mode 100644 index 0000000..2a58332 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/Class2$Class3.class.data diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/Class2.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/Class2.class.data Binary files differnew file mode 100644 index 0000000..1b72f03 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/Class2.class.data diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/pkg1/Class1.java.txt b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/pkg1/Class1.java.txt new file mode 100644 index 0000000..193ff3c --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/pkg1/Class1.java.txt @@ -0,0 +1,29 @@ +package pkg1; + +public class Class1 { + void method() { + } + + void method2(int foo) { + } + + void method3() { + } + + void method4() { + } + + void method5() { + } + + void method6() { + } + + void method7() { + } + + public static class Class4 extends Class1 { + void method() { // Not an error: same package + } + } +} diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/pkg2/Class2.java.txt b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/pkg2/Class2.java.txt new file mode 100644 index 0000000..651c021 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/src/pkg2/Class2.java.txt @@ -0,0 +1,33 @@ +package pkg2; + +import android.annotation.SuppressLint; +import pkg1.Class1; + +public class Class2 extends Class1 { + void method() { // Flag this as an accidental override + } + + void method2(String foo) { // not an override: different signature + } + + static void method4() { // not an override: static + } + + private void method3() { // not an override: private + } + + protected void method5() { // not an override: protected + } + + public void method6() { // not an override: public + } + + @SuppressLint("DalvikOverride") + public void method7() { // suppressed: no warning + } + + public class Class3 extends Object { + void method() { // Not an override: not a subclass + } + } +} |