diff options
39 files changed, 1439 insertions, 436 deletions
diff --git a/adtproductbuild/Android.mk b/adtproductbuild/Android.mk new file mode 100644 index 0000000..e3421d9 --- /dev/null +++ b/adtproductbuild/Android.mk @@ -0,0 +1,133 @@ +# Copyright 2012 The Android Open Source Project +# +# Makefile rules to build the ADT Eclipse IDE. +# This is invoked from sdk/eclipse/scripts/build_server.sh using +# something like "make PRODUCT-sdk-adt_eclipse_ide". +# +# Expected env vars: +# ADT_IDE_DEST_DIR: existing directory where to copy the IDE zip files. +# ADT_IDE_QUALIFIER: either a date or build number to incorporate in the zip names. + +# Expose the ADT Eclipse IDE build only for the SDK builds. +ifneq (,$(is_sdk_build)$(filter sdk sdk_x86 sdk_mips,$(TARGET_PRODUCT))) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := adt_eclipse_ide +LOCAL_MODULE_CLASS := EXECUTABLES +LOCAL_MODULE_TAGS := optional +LOCAL_IS_HOST_MODULE := true +include $(BUILD_SYSTEM)/base_rules.mk + +ADT_IDE_MODULE_DEPS := $(TOPDIR)sdk/adtproductbuild/$(LOCAL_MODULE) + +ADT_IDE_BUILD_LOG := $(TOPDIR)out/host/eclipse/adtproduct/build/adtproduct.log +ADT_IDE_ARTIFACT_DIR := $(TOPDIR)out/host/eclipse/adtproduct/build/I.RcpBuild +ADT_IDE_RELEASE_DIR := $(TOPDIR)out/host/eclipse/adtproduct/release + +ADT_IDE_JAVA_LIBS := $(shell $(TOPDIR)sdk/eclipse/scripts/create_all_symlinks.sh -d) +ADT_IDE_JAVA_DEPS := $(foreach m,$(ADT_IDE_JAVA_LIBS),$(HOST_OUT_JAVA_LIBRARIES)/$(m).jar) + +ADT_IDE_JAVA_TARGET := $(ADT_IDE_RELEASE_DIR)/adt_eclipse_ide_java_build + +# Common not-quite-phony rule to perform the eclipse build only once +# This invokes the java builder on eclipse. It generates multiple +# zipped versions (one per OS, all built at the same time) +# of the ide as specified in the build.properties file. +$(ADT_IDE_JAVA_TARGET) : $(TOPDIR)sdk/adtproductbuild/adt_eclipse_ide \ + $(TOPDIR)sdk/adtproductbuild/build.xml \ + $(TOPDIR)sdk/adtproductbuild/build.properties \ + $(ADT_IDE_JAVA_DEPS) + @if [[ ! -d $(TOPDIR)prebuilts/eclipse-build-deps ]]; then \ + echo "*** [adt_eclipse_ide] ERROR: Missing prebuilts/eclipse-build-deps directory. Make sure to run 'repo init -g all;repo sync' first."; \ + exit 1; \ + fi + $(hide)rm -rf $(TOPDIR)out/host/eclipse/adtproduct/build/plugins + $(hide)mkdir -p $(dir $@) + $(hide)$(TOPDIR)sdk/eclipse/scripts/create_all_symlinks.sh -c + $(hide)cd $(TOPDIR)sdk/adtproductbuild && \ + rm -f ../../$(ADT_IDE_BUILD_LOG) && mkdir -p ../../$(dir $(ADT_IDE_BUILD_LOG)) && \ + ( java -jar ../../external/eclipse-basebuilder/basebuilder-3.6.2/org.eclipse.releng.basebuilder/plugins/org.eclipse.equinox.launcher_1.1.0.v20100507.jar \ + org.eclipse.equinox.launcher.Main \ + -application org.eclipse.ant.core.antRunner \ + -configuration ../../out/host/eclipse/adtproduct/build/configuration \ + -data ../../out/host/eclipse/adtproduct/workspace \ + 2>&1 && \ + mv -f ../../$(ADT_IDE_BUILD_LOG) ../../$(ADT_IDE_BUILD_LOG).1 ) \ + | tee ../../$(ADT_IDE_BUILD_LOG) \ + | sed 's/^/IDE: /'; \ + if [[ -f ../../$(ADT_IDE_BUILD_LOG) ]]; then \ + echo "ADT IDE build failed. Full log:" ; \ + cat ../../$(ADT_IDE_BUILD_LOG) ; \ + exit 1 ; \ + fi + $(hide)$(ACP) -fp $(V) $(TOPDIR)sdk/adtproductbuild/adt_eclipse_ide $@ + +# Defines the zip filename generated for an OS specific android IDE. +define adt-ide-zip +$(ADT_IDE_RELEASE_DIR)/android-ide-$(ADT_IDE_QUALIFIER)-$(1).$(2).zip +endef + +# Defines the rule needed to make one of the OS specific android IDE. +# If ADT_IDE_DEST_DIR it also defines the rule to produce the final dest zip. +# $1 = the platform (linux|macosx|win32).(gtk|cocoa|win32) +# $2 = the architecture (x86 or x8_64). +# $3 = the src zip (from out/host/eclipse/artifacts/RcpBuild-...) +# $4 = the destination directory (where the unpacked eclipse is created) +# $5 = the destination zip with the zipped eclipse ide. +define mk-adt-ide-2 +$(5): $(ADT_IDE_JAVA_TARGET) + $(hide) \ + rm -rf $(V) $(4) && \ + rm -f $(V) $(5) && \ + mkdir -p $(4) && \ + unzip -q $(3) -d $(4) && \ + sed -i 's/org.eclipse.platform/com.android.ide.eclipse.adt.package.product/g' \ + $(4)/eclipse/$(if $(filter macosx.cocoa,$(1)),eclipse.app/Contents/MacOS/)eclipse.ini && \ + sed -i -e 's/org.eclipse.platform.ide/com.android.ide.eclipse.adt.package.product/g' \ + -e 's/org.eclipse.platform/com.android.ide.eclipse.adt.package/g' \ + $(4)/eclipse/configuration/config.ini + $(hide)cd $(4) && zip -9rq ../$(notdir $(5)) eclipse +ifneq (,$(ADT_IDE_DEST_DIR)) +$(ADT_IDE_DEST_DIR)/$(notdir $(5)): $(5) + @mkdir -p $(ADT_IDE_DEST_DIR) + $(hide)cp $(V) $(5) $(ADT_IDE_DEST_DIR)/$(notdir $(5)) + @echo "ADT IDE copied to $(ADT_IDE_DEST_DIR)/$(notdir $(5))" +else + @echo "ADT IDE available at $(5)" +endif +endef + +# Defines the rule needed to make one of the OS specific android IDE. +# This is just a convenience wrapper that calls mk-adt-ide-2 and presets +# the source and destination zip paths. +# It also sets the dependencies we need to produce the final dest zip. +# $1 = the platform (linux|macosx|win32).(gtk|cocoa|win32) +# $2 = the architecture (x86 or x8_64). +define mk-adt-ide +$(call mk-adt-ide-2,$(1),$(2), \ + $(ADT_IDE_ARTIFACT_DIR)/RcpBuild-$(1).$(2).zip, \ + $(ADT_IDE_RELEASE_DIR)/android-ide-$(1).$(2), \ + $(call adt-ide-zip,$(1),$(2))) +ADT_IDE_MODULE_DEPS += $(call adt-ide-zip,$(1),$(2)) +ifneq (,$(ADT_IDE_DEST_DIR)) +ADT_IDE_MODULE_DEPS += $(ADT_IDE_DEST_DIR)/$(notdir $(call adt-ide-zip,$(1),$(2))) +endif +endef + +$(eval $(call mk-adt-ide,linux.gtk,x86_64)) +$(eval $(call mk-adt-ide,macosx.cocoa,x86_64)) +$(eval $(call mk-adt-ide,win32.win32,x86_64)) + +# This rule triggers the build of the 3 ide zip files. +# The adt_eclipse_ide script is currently a platceholder used +# to detect when the build was completed. We may use it later +# as a launch script. +$(LOCAL_BUILT_MODULE) : $(ADT_IDE_MODULE_DEPS) + @mkdir -p $(dir $@) + $(hide)$(ACP) -fp $(V) $(TOPDIR)sdk/adtproductbuild/adt_eclipse_ide $@ + @echo "Packing of ADT IDE done" + +endif + diff --git a/adtproductbuild/Makefile b/adtproductbuild/Makefile deleted file mode 100644 index 64d30ef..0000000 --- a/adtproductbuild/Makefile +++ /dev/null @@ -1,36 +0,0 @@ -ADT_IDE_BUILD_LOG := out/host/eclipse/adtproduct/build/adtproduct.log -ADT_IDE_ARTIFACT_DIR := ../../out/host/eclipse/adtproduct/build/I.RcpBuild -ADT_IDE_RELEASE_DIR := ../../out/host/eclipse/adtproduct/release/ - -define release-ide - srczip=$(ADT_IDE_ARTIFACT_DIR)/RcpBuild-$(1).$(2).zip && \ - dstdir=$(ADT_IDE_RELEASE_DIR)/android-ide-$(1).$(2) && \ - rm -rf $(V) $$dstdir && \ - mkdir -p $$dstdir && \ - unzip -q $$srczip -d $$dstdir && \ - if [[ $(1) == "macosx.cocoa" ]]; then eclipseini=eclipse.app/Contents/MacOS/eclipse.ini; else eclipseini=eclipse.ini; fi && \ - sed -i 's/org.eclipse.platform/com.android.ide.eclipse.adt.package.product/g' $$dstdir/eclipse/$$eclipseini && \ - sed -i -e 's/org.eclipse.platform.ide/com.android.ide.eclipse.adt.package.product/g' \ - -e 's/org.eclipse.platform/com.android.ide.eclipse.adt.package/g' $$dstdir/eclipse/configuration/config.ini -endef - - -all: - rm -f ../../$(ADT_IDE_BUILD_LOG) && mkdir -p ../../$(dir $(ADT_IDE_BUILD_LOG)) && \ - ( java -jar ../../external/eclipse-basebuilder/basebuilder-3.6.2/org.eclipse.releng.basebuilder/plugins/org.eclipse.equinox.launcher_1.1.0.v20100507.jar \ - org.eclipse.equinox.launcher.Main \ - -application org.eclipse.ant.core.antRunner \ - -configuration ../../out/host/eclipse/adtproduct/build/configuration \ - -data ../../out/host/eclipse/adtproduct/workspace \ - 2>&1 && \ - mv -f ../../$(ADT_IDE_BUILD_LOG) ../../$(ADT_IDE_BUILD_LOG).1 ) \ - | tee ../../$(ADT_IDE_BUILD_LOG) \ - | sed '/SUCCESSFUL/d ; /\[java\]/!b label; s/\s\+\[java\]//; /^\s*$$/d; /Compiling/!d; :label /^\s*$$/d; s/^/ADT: /'; \ - if [[ -f ../../$(ADT_IDE_BUILD_LOG) ]]; then \ - echo "ADT IDE build failed. Full log:" ; \ - cat ../../$(ADT_IDE_BUILD_LOG) ; \ - exit 1 ; \ - fi - $(call release-ide,linux.gtk,x86_64) ; \ - $(call release-ide,macosx.cocoa,x86_64) ; \ - $(call release-ide,win32.win32,x86_64) ; \ diff --git a/adtproductbuild/adt_eclipse_ide b/adtproductbuild/adt_eclipse_ide new file mode 100644 index 0000000..ea3355c --- /dev/null +++ b/adtproductbuild/adt_eclipse_ide @@ -0,0 +1,3 @@ +#!/bin/bash +echo "Placeholder launcher script for the ADT Eclipse IDE." +exit 1 diff --git a/adtproductbuild/build.xml b/adtproductbuild/build.xml index 7851625..c91bff0 100644 --- a/adtproductbuild/build.xml +++ b/adtproductbuild/build.xml @@ -7,7 +7,7 @@ <!-- Source for target prebuilts --> <property name="deltaPackTargetSrcDir" value="${ANDROID_SRC}/prebuilts/eclipse/" /> - <property name="targetSrcDir" value="${ANDROID_SRC}/prebuilts/eclipse/" /> + <property name="targetSrcDir" value="${ANDROID_SRC}/prebuilts/eclipse-build-deps/" /> <!-- Location where build happens and resulting binaries are generated --> <property name="outDir" value="${ANDROID_SRC}/out/host/eclipse/adtproduct/" /> diff --git a/common/src/com/android/SdkConstants.java b/common/src/com/android/SdkConstants.java index 8c798c8..04fa987 100644 --- a/common/src/com/android/SdkConstants.java +++ b/common/src/com/android/SdkConstants.java @@ -945,7 +945,7 @@ public final class SdkConstants { public static final String ATTR_REF_PREFIX = "?attr/"; //$NON-NLS-1$ public static final String R_PREFIX = "R."; //$NON-NLS-1$ public static final String R_ID_PREFIX = "R.id."; //$NON-NLS-1$ - public static final String R_LAYOUT_RESOURCE_PREFIX = "R.layout."; //$NON-NLS-1$ + public static final String R_LAYOUT_RESOURCE_PREFIX = "R.layout."; //$NON-NLS-1$ public static final String R_DRAWABLE_PREFIX = "R.drawable."; //$NON-NLS-1$ public static final String R_ATTR_PREFIX = "R.attr."; //$NON-NLS-1$ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java index 426dbae..d0d328e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java @@ -17,7 +17,6 @@ package com.android.ide.eclipse.adt.internal.assetstudio; import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.DEFAULT_LAUNCHER_ICON; - import static java.awt.image.BufferedImage.TYPE_INT_ARGB; import com.android.annotations.NonNull; @@ -481,7 +480,7 @@ public class ConfigureAssetSetPage extends WizardPage implements SelectionListen // Initial image - use the most recently used image, or the default launcher // icon created in our default projects, if there if (mValues.imagePath != null) { - sImagePath = mValues.imagePath.getPath();; + sImagePath = mValues.imagePath.getPath(); } if (sImagePath == null) { IProject project = mValues.project; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java index 98f5317..253421f 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java @@ -560,8 +560,8 @@ public final class ProjectCallback extends LegacyCallback { } @Override - public AdapterBinding getAdapterBinding(final ResourceReference adapterView, final Object adapterCookie, - final Object viewObject) { + public AdapterBinding getAdapterBinding(final ResourceReference adapterView, + final Object adapterCookie, final Object viewObject) { // Look for user-recorded preference for layout to be used for previews if (adapterCookie instanceof UiViewElementNode) { UiViewElementNode uiNode = (UiViewElementNode) adapterCookie; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ActivityMenuListener.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ActivityMenuListener.java index 1f85a32..158a647 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ActivityMenuListener.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ActivityMenuListener.java @@ -88,7 +88,7 @@ class ActivityMenuListener extends SelectionAdapter { if (current != null) { MenuItem item = new MenuItem(menu, SWT.PUSH); - String label = ConfigurationChooser.getActivityLabel(current, true);; + String label = ConfigurationChooser.getActivityLabel(current, true); item.setText( String.format("Open %1$s...", label)); Image image = sharedImages.getImage(ISharedImages.IMG_OBJS_CUNIT); item.setImage(image); 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 2106f8d..2b5589b 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 @@ -17,11 +17,13 @@ package com.android.ide.eclipse.adt.internal.editors.layout.configuration; import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX; +import static com.android.SdkConstants.PREFIX_RESOURCE_REF; 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.resources.ResourceRepository; import com.android.ide.common.resources.configuration.DensityQualifier; import com.android.ide.common.resources.configuration.DeviceConfigHelper; import com.android.ide.common.resources.configuration.FolderConfiguration; @@ -30,13 +32,17 @@ import com.android.ide.common.resources.configuration.NightModeQualifier; import com.android.ide.common.resources.configuration.RegionQualifier; import com.android.ide.common.resources.configuration.ScreenDimensionQualifier; import com.android.ide.common.resources.configuration.ScreenOrientationQualifier; +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.manifest.ManifestInfo; import com.android.ide.eclipse.adt.internal.resources.ResourceHelper; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.resources.Density; import com.android.resources.NightMode; import com.android.resources.ScreenOrientation; +import com.android.resources.ScreenSize; import com.android.resources.UiMode; import com.android.sdklib.AndroidVersion; import com.android.sdklib.IAndroidTarget; @@ -49,6 +55,7 @@ import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.QualifiedName; import java.util.List; +import java.util.Map; /** * A {@linkplain Configuration} is a selection of device, orientation, theme, @@ -397,6 +404,7 @@ public class Configuration { */ public void setTheme(String theme) { mTheme = theme; + checkThemePrefix(); } /** @@ -507,6 +515,67 @@ public class Configuration { return sb.toString(); } + /** Returns the preferred theme, or null */ + @Nullable + String computePreferredTheme() { + IProject project = mConfigChooser.getProject(); + ManifestInfo manifest = ManifestInfo.get(project); + + // Look up the screen size for the current state + ScreenSize screenSize = null; + Device device = getDevice(); + if (device != null) { + List<State> states = device.getAllStates(); + for (State state : states) { + FolderConfiguration folderConfig = DeviceConfigHelper.getFolderConfig(state); + if (folderConfig != null) { + ScreenSizeQualifier qualifier = folderConfig.getScreenSizeQualifier(); + screenSize = qualifier.getValue(); + break; + } + } + } + + // Look up the default/fallback theme to use for this project (which + // depends on the screen size when no particular theme is specified + // in the manifest) + String defaultTheme = manifest.getDefaultTheme(getTarget(), screenSize); + + String preferred = defaultTheme; + if (getTheme() == null) { + // If we are rendering a layout in included context, pick the theme + // from the outer layout instead + + String activity = getActivity(); + if (activity != null) { + Map<String, String> activityThemes = manifest.getActivityThemes(); + preferred = activityThemes.get(activity); + } + if (preferred == null) { + preferred = defaultTheme; + } + setTheme(preferred); + } + + return preferred; + } + + private void checkThemePrefix() { + if (mTheme != null && !mTheme.startsWith(PREFIX_RESOURCE_REF)) { + if (mTheme.isEmpty()) { + computePreferredTheme(); + return; + } + ResourceRepository frameworkRes = mConfigChooser.getClient().getFrameworkResources(); + if (frameworkRes != null + && frameworkRes.hasResourceItem(ANDROID_STYLE_RESOURCE_PREFIX + mTheme)) { + mTheme = ANDROID_STYLE_RESOURCE_PREFIX + mTheme; + } else { + mTheme = STYLE_RESOURCE_PREFIX + mTheme; + } + } + } + /** * Initializes a string previously created with * {@link #toPersistentString()} @@ -555,6 +624,8 @@ public class Configuration { } else if (mTheme.startsWith(MARKER_PROJECT)) { mTheme = STYLE_RESOURCE_PREFIX + mTheme.substring(MARKER_PROJECT.length()); + } else { + checkThemePrefix(); } mUiMode = UiMode.getEnum(values[4]); @@ -604,7 +675,7 @@ public class Configuration { @Nullable static Pair<Locale, IAndroidTarget> loadRenderState(ConfigurationChooser chooser) { IProject project = chooser.getProject(); - if (!project.isAccessible()) { + if (project == null || !project.isAccessible()) { return null; } @@ -668,6 +739,9 @@ public class Configuration { */ void saveRenderState() { IProject project = mConfigChooser.getProject(); + if (project == null) { + return; + } try { // Generate a persistent string from locale+target StringBuilder sb = new StringBuilder(); @@ -700,7 +774,7 @@ public class Configuration { * @return an id for the given target; never null */ @NonNull - private static String targetToString(@NonNull IAndroidTarget target) { + public static String targetToString(@NonNull IAndroidTarget target) { return target.getFullName().replace(SEP, ""); //$NON-NLS-1$ } @@ -715,7 +789,7 @@ public class Configuration { * @return an {@link IAndroidTarget} that matches the given id, or null */ @Nullable - private static IAndroidTarget stringToTarget( + public static IAndroidTarget stringToTarget( @NonNull ConfigurationChooser chooser, @NonNull String id) { List<IAndroidTarget> targetList = chooser.getTargetList(); @@ -731,6 +805,30 @@ public class Configuration { } /** + * Returns an {@link IAndroidTarget} that corresponds to the given id that was + * originally returned by {@link #targetToString}. May be null, if the platform is no + * longer available, or if the platform list has not yet been initialized. + * + * @param id the id that corresponds to the desired platform + * @return an {@link IAndroidTarget} that matches the given id, or null + */ + @Nullable + public static IAndroidTarget stringToTarget( + @NonNull String id) { + Sdk currentSdk = Sdk.getCurrent(); + if (currentSdk != null) { + IAndroidTarget[] targets = currentSdk.getTargets(); + for (IAndroidTarget target : targets) { + if (id.equals(targetToString(target))) { + return target; + } + } + } + + return null; + } + + /** * Returns the {@link State} by the given name for the given {@link Device} * * @param device the device diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java index b512bcc..7412bf1 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java @@ -44,7 +44,6 @@ import com.android.ide.common.resources.configuration.FolderConfiguration; import com.android.ide.common.resources.configuration.LanguageQualifier; import com.android.ide.common.resources.configuration.RegionQualifier; import com.android.ide.common.resources.configuration.ResourceQualifier; -import com.android.ide.common.resources.configuration.ScreenSizeQualifier; import com.android.ide.common.sdk.LoadStatus; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.IconFactory; @@ -58,7 +57,6 @@ import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.resources.ResourceType; import com.android.resources.ScreenOrientation; -import com.android.resources.ScreenSize; import com.android.sdklib.AndroidVersion; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.devices.Device; @@ -298,19 +296,42 @@ public class ConfigurationChooser extends Composite addDisposeListener(this); } - IFile getEditedFile() { + /** + * Returns the edited file + * + * @return the file + */ + @Nullable + public IFile getEditedFile() { return mEditedFile; } - IProject getProject() { - return mEditedFile.getProject(); + /** + * Returns the project of the edited file + * + * @return the project + */ + @Nullable + public IProject getProject() { + if (mEditedFile != null) { + return mEditedFile.getProject(); + } else { + return null; + } } ConfigurationClient getClient() { return mClient; } - ProjectResources getResources() { + /** + * Returns the project resources for the project being configured by this + * chooser + * + * @return the project resources + */ + @Nullable + public ProjectResources getResources() { return mResources; } @@ -328,7 +349,7 @@ public class ConfigurationChooser extends Composite * * @return the project target */ - IAndroidTarget getProjectTarget() { + public IAndroidTarget getProjectTarget() { return mProjectTarget; } @@ -746,6 +767,7 @@ public class ConfigurationChooser extends Composite if (target != null) { targetData = Sdk.getCurrent().getTargetData(target); selectTarget(target); + mConfiguration.setTarget(target, true); } } @@ -1588,7 +1610,7 @@ public class ConfigurationChooser extends Composite String theme = mConfiguration.getTheme(); if (theme == null || theme.isEmpty() || mClient.getIncludedWithin() != null) { mConfiguration.setTheme(null); - computePreferredTheme(); + mConfiguration.computePreferredTheme(); } assert mConfiguration.getTheme() != null; } @@ -1680,6 +1702,14 @@ public class ConfigurationChooser extends Composite break; } } + if (!theme.startsWith(PREFIX_RESOURCE_REF)) { + // Arbitrary guess + if (theme.startsWith("Theme.")) { + theme = ANDROID_STYLE_RESOURCE_PREFIX + theme; + } else { + theme = STYLE_RESOURCE_PREFIX + theme; + } + } } // TODO: Handle the case where you have a theme persisted that isn't available?? @@ -1748,55 +1778,6 @@ public class ConfigurationChooser extends Composite } } - /** Returns the preferred theme, or null */ - @Nullable - String computePreferredTheme() { - if (mClient == null) { - return null; - } - - IProject project = mEditedFile.getProject(); - ManifestInfo manifest = ManifestInfo.get(project); - - // Look up the screen size for the current state - ScreenSize screenSize = null; - Device device = mConfiguration.getDevice(); - if (device != null) { - List<State> states = device.getAllStates(); - for (State state : states) { - FolderConfiguration folderConfig = DeviceConfigHelper.getFolderConfig(state); - if (folderConfig != null) { - ScreenSizeQualifier qualifier = folderConfig.getScreenSizeQualifier(); - screenSize = qualifier.getValue(); - break; - } - } - } - - // Look up the default/fallback theme to use for this project (which - // depends on the screen size when no particular theme is specified - // in the manifest) - String defaultTheme = manifest.getDefaultTheme(mConfiguration.getTarget(), screenSize); - - String preferred = defaultTheme; - if (mConfiguration.getTheme() == null) { - // If we are rendering a layout in included context, pick the theme - // from the outer layout instead - - String activity = mConfiguration.getActivity(); - if (activity != null) { - Map<String, String> activityThemes = manifest.getActivityThemes(); - preferred = activityThemes.get(activity); - } - if (preferred == null) { - preferred = defaultTheme; - } - mConfiguration.setTheme(preferred); - } - - return preferred; - } - @Nullable private String getPreferredActivity(@NonNull IFile file) { // Store/restore the activity context in the config state to help with diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java index 239f396..0f6c9eb 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java @@ -106,7 +106,7 @@ class ThemeMenuAction extends SubmenuAction { manager.add(new Separator()); } - String preferred = configChooser.computePreferredTheme(); + String preferred = configuration.computePreferredTheme(); if (preferred != null && !preferred.equals(currentTheme)) { manager.add(new SelectThemeAction(configChooser, ResourceHelper.styleToTheme(preferred), diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java index 60e9920..5ca8e9c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java @@ -377,11 +377,15 @@ public class PreviewIconFactory { * * @return a pair of possibly null color descriptions */ + @NonNull private Pair<RGB, RGB> getColorsFromTheme() { RGB background = null; RGB foreground = null; ResourceResolver resources = mPalette.getEditor().getResourceResolver(); + if (resources == null) { + return Pair.of(background, foreground); + } StyleResourceValue theme = resources.getCurrentTheme(); if (theme != null) { background = resolveThemeColor(resources, "windowBackground"); //$NON-NLS-1$ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java index 72577a5..d2d7878 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java @@ -137,7 +137,7 @@ class XmlPropertyEditor extends AbstractTextPropertyEditor { // TODO: do I have to strip off the @ too? isFramework = isFramework || value.startsWith(ANDROID_PREFIX) - || value.startsWith(ANDROID_THEME_PREFIX);; + || value.startsWith(ANDROID_THEME_PREFIX); ResourceValue v = resolver.findResValue(text, isFramework); if (v != null && !value.equals(v.getValue())) { resValue = v; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFix.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFix.java index 7683f8d..0b074bb 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFix.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFix.java @@ -24,6 +24,7 @@ import com.android.tools.lint.checks.DetectMissingPrefix; import com.android.tools.lint.checks.HardcodedValuesDetector; import com.android.tools.lint.checks.InefficientWeightDetector; import com.android.tools.lint.checks.ManifestOrderDetector; +import com.android.tools.lint.checks.MissingIdDetector; import com.android.tools.lint.checks.ObsoleteLayoutParamsDetector; import com.android.tools.lint.checks.PxUsageDetector; import com.android.tools.lint.checks.ScrollViewChildDetector; @@ -150,6 +151,7 @@ abstract class LintFix implements ICompletionProposal { sFixes.put(AccessibilityDetector.ISSUE.getId(), SetAttributeFix.class); sFixes.put(InefficientWeightDetector.BASELINE_WEIGHTS.getId(), SetAttributeFix.class); sFixes.put(ManifestOrderDetector.ALLOW_BACKUP.getId(), SetAttributeFix.class); + sFixes.put(MissingIdDetector.ISSUE.getId(), SetAttributeFix.class); sFixes.put(HardcodedValuesDetector.ISSUE.getId(), ExtractStringFix.class); sFixes.put(UselessViewDetector.USELESS_LEAF.getId(), RemoveUselessViewFix.class); sFixes.put(UselessViewDetector.USELESS_PARENT.getId(), RemoveUselessViewFix.class); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetAttributeFix.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetAttributeFix.java index 1d743b8..3227bc3 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetAttributeFix.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetAttributeFix.java @@ -18,19 +18,26 @@ package com.android.ide.eclipse.adt.internal.lint; import static com.android.SdkConstants.ATTR_ALLOW_BACKUP; import static com.android.SdkConstants.ATTR_BASELINE_ALIGNED; import static com.android.SdkConstants.ATTR_CONTENT_DESCRIPTION; +import static com.android.SdkConstants.ATTR_ID; import static com.android.SdkConstants.ATTR_INPUT_TYPE; import static com.android.SdkConstants.ATTR_PERMISSION; import static com.android.SdkConstants.ATTR_TRANSLATABLE; +import static com.android.SdkConstants.NEW_ID_PREFIX; import static com.android.SdkConstants.VALUE_FALSE; +import com.android.ide.eclipse.adt.AdtUtils; +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; import com.android.tools.lint.checks.AccessibilityDetector; import com.android.tools.lint.checks.InefficientWeightDetector; import com.android.tools.lint.checks.ManifestOrderDetector; +import com.android.tools.lint.checks.MissingIdDetector; import com.android.tools.lint.checks.SecurityDetector; import com.android.tools.lint.checks.TextFieldDetector; import com.android.tools.lint.checks.TranslationDetector; import org.eclipse.core.resources.IMarker; +import org.eclipse.ui.IEditorPart; /** Shared fix class for various builtin attributes */ final class SetAttributeFix extends SetPropertyFix { @@ -52,6 +59,8 @@ final class SetAttributeFix extends SetPropertyFix { return ATTR_TRANSLATABLE; } else if (mId.equals(ManifestOrderDetector.ALLOW_BACKUP.getId())) { return ATTR_ALLOW_BACKUP; + } else if (mId.equals(MissingIdDetector.ISSUE.getId())) { + return ATTR_ID; } else { assert false : mId; return ""; @@ -81,6 +90,8 @@ final class SetAttributeFix extends SetPropertyFix { return "Mark this as a non-translatable resource"; } else if (mId.equals(ManifestOrderDetector.ALLOW_BACKUP.getId())) { return "Set the allowBackup attribute to true or false"; + } else if (mId.equals(MissingIdDetector.ISSUE.getId())) { + return "Set the ID attribute"; } else { assert false : mId; return ""; @@ -120,6 +131,15 @@ final class SetAttributeFix extends SetPropertyFix { return VALUE_FALSE; } else if (mId.equals(TranslationDetector.MISSING.getId())) { return VALUE_FALSE; + } else if (mId.equals(MissingIdDetector.ISSUE.getId())) { + IEditorPart editor = AdtUtils.getActiveEditor(); + if (editor instanceof AndroidXmlEditor) { + AndroidXmlEditor xmlEditor = (AndroidXmlEditor) editor; + return DescriptorsUtils.getFreeWidgetId(xmlEditor.getUiRootNode(), + "fragment"); //$NON-NLS-1$ + } else { + return NEW_ID_PREFIX; + } } return super.getProposal(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java index 53b6c3c..434384c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java @@ -58,6 +58,7 @@ import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; @@ -66,6 +67,7 @@ import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.ToolFactory; import org.eclipse.jdt.core.formatter.CodeFormatter; import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ltk.core.refactoring.NullChange; @@ -856,7 +858,7 @@ class TemplateHandler { contents = format(mProject, contents, to); IFile targetFile = getTargetFile(to); - TextFileChange change = createTextChange(targetFile); + TextFileChange change = createNewFileChange(targetFile); MultiTextEdit rootEdit = new MultiTextEdit(); rootEdit.addChild(new InsertEdit(0, contents)); change.setEdit(rootEdit); @@ -908,7 +910,7 @@ class TemplateHandler { return contents; } - private static TextFileChange createTextChange(IFile targetFile) { + private static TextFileChange createNewFileChange(IFile targetFile) { String fileName = targetFile.getName(); String message; if (targetFile.exists()) { @@ -917,7 +919,29 @@ class TemplateHandler { message = String.format("Create %1$s", fileName); } - TextFileChange change = new TextFileChange(message, targetFile); + TextFileChange change = new TextFileChange(message, targetFile) { + @Override + protected IDocument acquireDocument(IProgressMonitor pm) throws CoreException { + IDocument document = super.acquireDocument(pm); + + // In our case, we know we *always* use this TextFileChange + // to *create* files, we're not appending to existing files. + // However, due to the following bug we can end up with cached + // contents of previously deleted files that happened to have the + // same file name: + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=390402 + // Therefore, as a workaround, wipe out the cached contents here + if (document.getLength() > 0) { + try { + document.replace(0, document.getLength(), ""); + } catch (BadLocationException e) { + // pass + } + } + + return document; + } + }; change.setTextType(fileName.substring(fileName.lastIndexOf('.') + 1)); return change; } @@ -989,7 +1013,7 @@ class TemplateHandler { String newFile = Files.toString(src, Charsets.UTF_8); newFile = format(mProject, newFile, path); - TextFileChange addFile = createTextChange(file); + TextFileChange addFile = createNewFileChange(file); addFile.setEdit(new InsertEdit(0, newFile)); mTextChanges.add(addFile); } else { diff --git a/eclipse/scripts/build_server.sh b/eclipse/scripts/build_server.sh index 4c27673..b3b86b7 100755 --- a/eclipse/scripts/build_server.sh +++ b/eclipse/scripts/build_server.sh @@ -6,7 +6,7 @@ # $2: Optional build number. If present, will be appended to the date qualifier. # The build number cannot contain spaces *nor* periods (dashes are ok.) # -z: Optional, prevents the final zip and leaves the udate-site directory intact. -# -i: Optional, if present, the Google internal update site will be built. Otherwise, +# -i: Optional, if present, the Google internal update site will be built. Otherwise, # the external site will be built # Workflow: # - make dx, ddms, ping @@ -57,16 +57,16 @@ function check_params() { # Check dest dir exists [ -n "$DEST_DIR" ] || die "Usage: $0 <destination-directory> [build-number]" [ -d "$DEST_DIR" ] || die "Destination directory $DEST_DIR must exist." -} - -function build_plugin { - sdk/eclipse/scripts/create_all_symlinks.sh # Qualifier is "v" followed by date/time in YYYYMMDDHHSS format and the optional # build number. DATE=`date +v%Y%m%d%H%M` QUALIFIER="$DATE" [ -n "$BUILD_NUMBER" ] && QUALIFIER="${QUALIFIER}-${BUILD_NUMBER}" +} + +function build_plugin() { + sdk/eclipse/scripts/create_all_symlinks.sh # Compute the final directory name and remove any leftovers from previous # runs if any. @@ -106,6 +106,16 @@ function build_plugin { fi } +function build_adt_ide() { + ADT_IDE_DEST_DIR="$DEST_DIR" \ + ADT_IDE_QUALIFIER="$QUALIFIER" \ + make PRODUCT-sdk-adt_eclipse_ide +} + get_params "$@" check_params -build_plugin +( build_plugin ) +if [[ -z $INTERNAL_BUILD ]]; then + ( build_adt_ide ) +fi + diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ArraySizeDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ArraySizeDetector.java index 771c247..8898507 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ArraySizeDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ArraySizeDetector.java @@ -35,6 +35,8 @@ import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.XmlContext; import com.android.utils.Pair; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; import org.w3c.dom.Attr; import org.w3c.dom.Element; @@ -78,7 +80,9 @@ public class ArraySizeDetector extends ResourceXmlDetector { ArraySizeDetector.class, Scope.ALL_RESOURCES_SCOPE); - private Map<File, Pair<String, Integer>> mFileToArrayCount; + //private Map<File, List<Pair<String, Integer>>> mFileToArrayCount; + //private Map<File, List<Pair<String, Integer>>> mFileToArrayCount; + private Multimap<File, Pair<String, Integer>> mFileToArrayCount; /** Locations for each array name. Populated during phase 2, if necessary */ private Map<String, Location> mLocations; @@ -107,7 +111,7 @@ public class ArraySizeDetector extends ResourceXmlDetector { @Override public void beforeCheckProject(@NonNull Context context) { if (context.getPhase() == 1) { - mFileToArrayCount = new HashMap<File, Pair<String,Integer>>(30); + mFileToArrayCount = ArrayListMultimap.create(30, 5); } } @@ -125,35 +129,38 @@ public class ArraySizeDetector extends ResourceXmlDetector { Collections.sort(keys); for (File file : keys) { - Pair<String, Integer> pair = mFileToArrayCount.get(file); - String name = pair.getFirst(); - if (alreadyReported.contains(name)) { - continue; - } - Integer count = pair.getSecond(); - - Integer current = countMap.get(name); - if (current == null) { - countMap.put(name, count); - fileMap.put(name, file); - } else if (!count.equals(current)) { - alreadyReported.add(name); - - if (mLocations == null) { - mLocations = new HashMap<String, Location>(); - mDescriptions = new HashMap<String, String>(); + Collection<Pair<String, Integer>> pairs = mFileToArrayCount.get(file); + for (Pair<String, Integer> pair : pairs) { + String name = pair.getFirst(); + + if (alreadyReported.contains(name)) { + continue; + } + Integer count = pair.getSecond(); + + Integer current = countMap.get(name); + if (current == null) { + countMap.put(name, count); + fileMap.put(name, file); + } else if (!count.equals(current)) { + alreadyReported.add(name); + + if (mLocations == null) { + mLocations = new HashMap<String, Location>(); + mDescriptions = new HashMap<String, String>(); + } + mLocations.put(name, null); + + String thisName = file.getParentFile().getName() + File.separator + + file.getName(); + File otherFile = fileMap.get(name); + String otherName = otherFile.getParentFile().getName() + File.separator + + otherFile.getName(); + String message = String.format( + "Array %1$s has an inconsistent number of items (%2$d in %3$s, %4$d in %5$s)", + name, count, thisName, current, otherName); + mDescriptions.put(name, message); } - mLocations.put(name, null); - - String thisName = file.getParentFile().getName() + File.separator - + file.getName(); - File otherFile = fileMap.get(name); - String otherName = otherFile.getParentFile().getName() + File.separator - + otherFile.getName(); - String message = String.format( - "Array %1$s has an inconsistent number of items (%2$d in %3$s, %4$d in %5$s)", - name, count, thisName, current, otherName); - mDescriptions.put(name, message); } } 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 476ebf9..70d741e 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 = 111; + final int initialCapacity = 113; List<Issue> issues = new ArrayList<Issue>(initialCapacity); issues.add(AccessibilityDetector.ISSUE); @@ -94,6 +94,7 @@ public class BuiltinIssueRegistry extends IssueRegistry { issues.add(MissingClassDetector.MISSING); issues.add(MissingClassDetector.INSTANTIATABLE); issues.add(MissingClassDetector.INNERCLASS); + issues.add(MissingIdDetector.ISSUE); issues.add(HandlerDetector.ISSUE); issues.add(FragmentDetector.ISSUE); issues.add(TranslationDetector.EXTRA); @@ -104,6 +105,7 @@ public class BuiltinIssueRegistry extends IssueRegistry { issues.add(ProguardDetector.SPLITCONFIG); issues.add(PxUsageDetector.PX_ISSUE); issues.add(PxUsageDetector.DP_ISSUE); + issues.add(PxUsageDetector.IN_MM_ISSUE); issues.add(TextFieldDetector.ISSUE); issues.add(TextViewDetector.ISSUE); issues.add(UnusedResourceDetector.ISSUE); diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/MissingIdDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MissingIdDetector.java new file mode 100644 index 0000000..b02e44a --- /dev/null +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MissingIdDetector.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.lint.checks; + +import static com.android.SdkConstants.ANDROID_URI; +import static com.android.SdkConstants.ATTR_ID; +import static com.android.SdkConstants.ATTR_TAG; +import static com.android.SdkConstants.VIEW_FRAGMENT; + +import com.android.annotations.NonNull; +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.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.Element; + +import java.util.Collection; +import java.util.Collections; + +/** + * Check which looks for missing id's in views where they are probably needed + */ +public class MissingIdDetector extends LayoutDetector { + /** The main issue discovered by this detector */ + public static final Issue ISSUE = Issue.create( + "MissingId", //$NON-NLS-1$ + "Ensures that XML tags like <fragment> specify an id or tag attribute", + + "If you do not specify an android:id or an android:tag attribute on a " + + "<fragment> element, then if the activity is restarted (for example for " + + "an orientation rotation) you may lose state. From the fragment " + + "documentation:\n" + + "\n" + + "\"Each fragment requires a unique identifier that the system can use " + + "to restore the fragment if the activity is restarted (and which you can " + + "use to capture the fragment to perform transactions, such as remove it). " + + "* Supply the android:id attribute with a unique ID.\n" + + "* Supply the android:tag attribute with a unique string.\n" + + "If you provide neither of the previous two, the system uses the ID of the " + + "container view.", + + Category.CORRECTNESS, + 5, + Severity.WARNING, + MissingIdDetector.class, + Scope.RESOURCE_FILE_SCOPE) + .setMoreInfo("http://developer.android.com/guide/components/fragments.html"); //$NON-NLS-1$ + + /** Constructs a new {@link MissingIdDetector} */ + public MissingIdDetector() { + } + + @Override + public @NonNull Speed getSpeed() { + return Speed.FAST; + } + + @Override + public Collection<String> getApplicableElements() { + return Collections.singletonList(VIEW_FRAGMENT); + } + + @Override + public void visitElement(@NonNull XmlContext context, @NonNull Element element) { + if (!element.hasAttributeNS(ANDROID_URI, ATTR_ID) && + !element.hasAttributeNS(ANDROID_URI, ATTR_TAG)) { + context.report(ISSUE, element, context.getLocation(element), + "This <fragment> tag should specify an id or a tag to preserve state " + + "across activity restarts", null); + } + } +} diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/PxUsageDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/PxUsageDetector.java index ef2768d..d1c5c63 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/PxUsageDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/PxUsageDetector.java @@ -22,6 +22,8 @@ import static com.android.SdkConstants.TAG_ITEM; import static com.android.SdkConstants.TAG_STYLE; import static com.android.SdkConstants.UNIT_DIP; import static com.android.SdkConstants.UNIT_DP; +import static com.android.SdkConstants.UNIT_IN; +import static com.android.SdkConstants.UNIT_MM; import static com.android.SdkConstants.UNIT_PX; import com.android.annotations.NonNull; @@ -48,7 +50,7 @@ import java.util.Collections; * Also look for non-"sp" text sizes. */ public class PxUsageDetector extends LayoutDetector { - /** The main issue discovered by this detector */ + /** Using px instead of dp */ public static final Issue PX_ISSUE = Issue.create( "PxUsage", //$NON-NLS-1$ "Looks for use of the \"px\" dimension", @@ -69,7 +71,24 @@ public class PxUsageDetector extends LayoutDetector { Scope.RESOURCE_FILE_SCOPE).setMoreInfo( "http://developer.android.com/guide/practices/screens_support.html#screen-independence"); //$NON-NLS-1$ - /** The main issue discovered by this detector */ + /** Using mm/in instead of dp */ + public static final Issue IN_MM_ISSUE = Issue.create( + "InOrMmUsage", //$NON-NLS-1$ + "Looks for use of the \"mm\" or \"in\" dimensions", + + "Avoid using `mm` (millimeters) or `in` (inches) as the unit for dimensions.\n" + + "\n" + + "While it should work in principle, unfortunately many devices do not report " + + "the correct true physical density, which means that the dimension calculations " + + "won't work correctly. You are better off using `dp` (and for font sizes, `sp`.)", + + Category.CORRECTNESS, + 4, + Severity.WARNING, + PxUsageDetector.class, + Scope.RESOURCE_FILE_SCOPE); + + /** Using sp instead of dp */ public static final Issue DP_ISSUE = Issue.create( "SpUsage", //$NON-NLS-1$ "Looks for uses of \"dp\" instead of \"sp\" dimensions for text sizes", @@ -86,7 +105,7 @@ public class PxUsageDetector extends LayoutDetector { "font size settings are not respected, so consider adjusting the layout itself " + "to be more flexible.", Category.CORRECTNESS, - 2, + 3, Severity.WARNING, PxUsageDetector.class, Scope.RESOURCE_FILE_SCOPE).setMoreInfo( @@ -134,6 +153,19 @@ public class PxUsageDetector extends LayoutDetector { context.report(PX_ISSUE, attribute, context.getLocation(attribute), "Avoid using \"px\" as units; use \"dp\" instead", null); } + } else if (value.endsWith(UNIT_MM) && value.matches("\\d+mm") //$NON-NLS-1$ + || value.endsWith(UNIT_IN) && value.matches("\\d+in")) { //$NON-NLS-1$ + if (value.charAt(0) == '0') { + // 0mm == 0in == 0dp + return; + } + if (context.isEnabled(IN_MM_ISSUE)) { + String unit = value.substring(value.length() - 2); + context.report(IN_MM_ISSUE, attribute, context.getLocation(attribute), String.format( + "Avoid using \"%1$s\" as units " + + "(it does not work accurately on all devices); use \"dp\" instead", unit), + null); + } } else if (ATTR_TEXT_SIZE.equals(attribute.getLocalName()) && (value.endsWith(UNIT_DP) || value.endsWith(UNIT_DIP)) && (value.matches("\\d+di?p"))) { @@ -183,6 +215,18 @@ public class PxUsageDetector extends LayoutDetector { "Avoid using \"px\" as units; use \"dp\" instead", null); } } + } else if (c == 'm' && text.charAt(j - 1) == 'm' || + c == 'n' && text.charAt(j - 1) == 'i') { + text = text.trim(); + String unit = text.substring(text.length() - 2); + if (text.matches("\\d+" + unit)) { //$NON-NLS-1$ + if (context.isEnabled(IN_MM_ISSUE)) { + context.report(IN_MM_ISSUE, item, context.getLocation(textNode), + String.format("Avoid using \"%1$s\" as units " + + "(it does not work accurately on all devices); " + + "use \"dp\" instead", unit), null); + } + } } else if (c == 'p' && (text.charAt(j - 1) == 'd' || text.charAt(j - 1) == 'i')) { // ends with dp or di text = text.trim(); diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ArraySizeDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ArraySizeDetectorTest.java index 629f43c..127e7f8 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ArraySizeDetectorTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/ArraySizeDetectorTest.java @@ -47,6 +47,19 @@ public class ArraySizeDetectorTest extends AbstractCheckTest { "res/values-es/strings.xml")); } + public void testMultipleArrays() throws Exception { + assertEquals( + "res/values/stringarrays.xml:3: Warning: Array map_density_desc has an inconsistent number of items (5 in values/stringarrays.xml, 1 in values-it/stringarrays.xml) [InconsistentArrays]\n" + + " <string-array name=\"map_density_desc\">\n" + + " ^\n" + + " res/values-it/stringarrays.xml:6: Declaration with array size (1)\n" + + "0 errors, 1 warnings\n", + + lintProject( + "res/values-it/stringarrays.xml", + "res/values/stringarrays.xml")); + } + public void testArraySizesSuppressed() throws Exception { assertEquals( "No warnings.", diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MissingIdDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MissingIdDetectorTest.java new file mode 100644 index 0000000..ac6b6c0 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MissingIdDetectorTest.java @@ -0,0 +1,36 @@ +/* + * 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 MissingIdDetectorTest extends AbstractCheckTest { + @Override + protected Detector getDetector() { + return new MissingIdDetector(); + } + + public void test() throws Exception { + assertEquals( + "res/layout/fragment.xml:7: Warning: This <fragment> tag should specify an id or a tag to preserve state across activity restarts [MissingId]\n" + + " <fragment\n" + + " ^\n" + + "0 errors, 1 warnings\n", + + lintProject("res/layout/fragment.xml")); + } +} diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/PxUsageDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/PxUsageDetectorTest.java index 77ebd70..7b5dd0c 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/PxUsageDetectorTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/PxUsageDetectorTest.java @@ -27,11 +27,16 @@ public class PxUsageDetectorTest extends AbstractCheckTest { public void testPx() throws Exception { assertEquals( + "res/layout/now_playing_after.xml:49: Warning: Avoid using \"mm\" as units (it does not work accurately on all devices); use \"dp\" instead [InOrMmUsage]\n" + + " android:layout_width=\"100mm\"\n" + + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + + "res/layout/now_playing_after.xml:50: Warning: Avoid using \"in\" as units (it does not work accurately on all devices); use \"dp\" instead [InOrMmUsage]\n" + + " android:layout_height=\"120in\"\n" + + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + "res/layout/now_playing_after.xml:41: Warning: Avoid using \"px\" as units; use \"dp\" instead [PxUsage]\n" + " android:layout_width=\"1px\"\n" + " ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + - "0 errors, 1 warnings\n" + - "", + "0 errors, 3 warnings\n", lintFiles("res/layout/now_playing_after.xml")); } @@ -43,13 +48,25 @@ public class PxUsageDetectorTest extends AbstractCheckTest { "res/layout/textsize.xml:16: Warning: Should use \"sp\" instead of \"dp\" for text sizes [SpUsage]\n" + " android:textSize=\"14dip\" />\n" + " ~~~~~~~~~~~~~~~~~~~~~~~~\n" + - "0 errors, 2 warnings\n" + - "", + "0 errors, 2 warnings\n", + lintFiles("res/layout/textsize.xml")); } public void testStyles() throws Exception { assertEquals( + "res/values/pxsp.xml:23: Warning: Avoid using \"mm\" as units (it does not work accurately on all devices); use \"dp\" instead [InOrMmUsage]\n" + + " <item name=\"android:textSize\">50mm</item>\n" + + " ^\n" + + "res/values/pxsp.xml:25: Warning: Avoid using \"in\" as units (it does not work accurately on all devices); use \"dp\" instead [InOrMmUsage]\n" + + " 50in\n" + + " ^\n" + + "res/values/pxsp.xml:6: Warning: Should use \"sp\" instead of \"dp\" for text sizes [SpUsage]\n" + + " <item name=\"android:textSize\">50dp</item>\n" + + " ^\n" + + "res/values/pxsp.xml:12: Warning: Should use \"sp\" instead of \"dp\" for text sizes [SpUsage]\n" + + " <item name=\"android:textSize\"> 50dip </item>\n" + + " ^\n" + "res/values/pxsp.xml:9: Warning: Avoid using \"px\" as units; use \"dp\" instead [PxUsage]\n" + " <item name=\"android:textSize\">50px</item>\n" + " ^\n" + @@ -59,14 +76,7 @@ public class PxUsageDetectorTest extends AbstractCheckTest { "res/values/pxsp.xml:18: Warning: Avoid using \"px\" as units; use \"dp\" instead [PxUsage]\n" + " <item name=\"android:paddingTop\">50px</item>\n" + " ^\n" + - "res/values/pxsp.xml:6: Warning: Should use \"sp\" instead of \"dp\" for text sizes [SpUsage]\n" + - " <item name=\"android:textSize\">50dp</item>\n" + - " ^\n" + - "res/values/pxsp.xml:12: Warning: Should use \"sp\" instead of \"dp\" for text sizes [SpUsage]\n" + - " <item name=\"android:textSize\"> 50dip </item>\n" + - " ^\n" + - "0 errors, 5 warnings\n" + - "", + "0 errors, 7 warnings\n", lintFiles("res/values/pxsp.xml")); } diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/fragment.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/fragment.xml new file mode 100644 index 0000000..bec72b2 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/fragment.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" > + + <fragment + android:name="android.app.ListFragment" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <fragment + android:name="android.app.DialogFragment" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:tag="mytag" /> + + <fragment + android:id="@+id/fragment3" + android:name="android.preference.PreferenceFragment" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + +</LinearLayout> diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/now_playing_after.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/now_playing_after.xml index 367a044..64f681c 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/now_playing_after.xml +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/now_playing_after.xml @@ -46,8 +46,8 @@ android:id="@+id/now_playing_more" android:src="@drawable/ic_now_playing_logo" android:padding="12dip" - android:layout_width="wrap_content" - android:layout_height="fill_parent" + android:layout_width="100mm" + android:layout_height="120in" android:onClick="onNowPlayingLogoClick" android:scaleType="center" /> </LinearLayout> diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values-it/stringarrays.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values-it/stringarrays.xml new file mode 100644 index 0000000..7bd83e9 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values-it/stringarrays.xml @@ -0,0 +1,12 @@ +<?xml version='1.0' encoding='utf-8'?> +<resources> + <string-array name="track_type_desc"> + <item>Pendenza</item> + </string-array> + <string-array name="map_density_desc"> + <item>Automatico (mappa leggibile su display HD)</item> + </string-array> + <string-array name="cache_size_desc"> + <item>Piccolo (100)</item> + </string-array> +</resources>
\ No newline at end of file diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/pxsp.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/pxsp.xml index 9f334a0..91bf06b 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/pxsp.xml +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/pxsp.xml @@ -2,13 +2,13 @@ <style name="Style1"> <item name="android:textSize">50sp</item> </style> - <style name="Style1"> + <style name="Style2"> <item name="android:textSize">50dp</item> </style> - <style name="Style1"> + <style name="Style3"> <item name="android:textSize">50px</item> </style> - <style name="Style1"> + <style name="Style4"> <item name="android:textSize"> 50dip </item> </style> @@ -19,4 +19,11 @@ <item name="android:paddingBottom">50dip</item> </style> + <style name="Style6"> + <item name="android:textSize">50mm</item> + <item name="android:textSize"> + 50in + </item> + </style> + </resources> diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/stringarrays.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/stringarrays.xml new file mode 100644 index 0000000..6405580 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/values/stringarrays.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string-array name="map_density_desc"> + <item>Automatic (readable map on HD displays)</item> + <item>1 map pixel = 1 screen pixel</item> + <item>1 map pixel = 1.25 screen pixels</item> + <item>1 map pixel = 1.5 screen pixels</item> + <item>1 map pixel = 2 screen pixels</item> + </string-array> + <string-array name="spatial_resolution_desc"> + <item>5m/yd (fine, default)</item> + </string-array> +</resources>
\ No newline at end of file diff --git a/manifmerger/src/com/android/manifmerger/ManifestMerger.java b/manifmerger/src/com/android/manifmerger/ManifestMerger.java index e4c3d6f..688cecd 100755 --- a/manifmerger/src/com/android/manifmerger/ManifestMerger.java +++ b/manifmerger/src/com/android/manifmerger/ManifestMerger.java @@ -32,9 +32,9 @@ import org.w3c.dom.NodeList; import java.io.File; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.TreeMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -486,7 +486,7 @@ public class ManifestMerger { boolean found = false; for (Element dest : findElements(mMainDoc, path)) { - if (compareElements(src, dest, false, null /*diff*/, null /*keyAttr*/)) { + if (compareElements(dest, src, false, null /*diff*/, null /*keyAttr*/)) { found = true; break; } @@ -566,7 +566,7 @@ public class ManifestMerger { for (Element dest : dests) { // If there's already a similar node in the destination, check it's identical. StringBuilder diff = new StringBuilder(); - if (compareElements(src, dest, false, diff, keyAttr)) { + if (compareElements(dest, src, false, diff, keyAttr)) { // Same element. Skip. if (warnDups) { mLog.conflict(Severity.INFO, @@ -1192,11 +1192,13 @@ public class ManifestMerger { } /** - * Compares two {@link Element}s recursively. They must be identical with the same - * structure and order. Whitespace and comments are ignored. + * Compares two {@link Element}s recursively. + * They must be identical with the same structure. + * Order should not matter. + * Whitespace and comments are ignored. * - * @param e1 The first element to compare. - * @param e2 The second element to compare with. + * @param expected The first element to compare. + * @param actual The second element to compare with. * @param nextSiblings If true, will also compare the following siblings. * If false, it will just compare the given node. * @param diff An optional {@link StringBuilder} where to accumulate a diff output. @@ -1204,197 +1206,22 @@ public class ManifestMerger { * @return True if {@code e1} and {@code e2} are equal. */ private boolean compareElements( - @NonNull Node e1, - @NonNull Node e2, + @NonNull Node expected, + @NonNull Node actual, boolean nextSiblings, @Nullable StringBuilder diff, @Nullable String keyAttr) { - return compareElements(e1, e2, nextSiblings, diff, 0, keyAttr); - } - - /** - * Do not call directly. This is an implementation detail for - * {@link #compareElements(Node, Node, boolean, StringBuilder, String)}. - */ - private boolean compareElements( - @NonNull Node e1, - @NonNull Node e2, - boolean nextSiblings, - @Nullable StringBuilder diff, - int diffOffset, - @Nullable String keyAttr) { - while(true) { - // Find the next non-whitespace text or non-comment in e1. - while (e1 != null) { - short t = e1.getNodeType(); - - if (t == Node.COMMENT_NODE) { - e1 = e1.getNextSibling(); - } else if (t == Node.TEXT_NODE) { - String s = e1.getNodeValue().trim(); - if (s.length() == 0) { - e1 = e1.getNextSibling(); - } else { - break; - } - } else { - break; - } - } - - // Find the next non-whitespace text or non-comment in e2. - while (e2 != null) { - short t = e2.getNodeType(); - - if (t == Node.COMMENT_NODE) { - e2 = e2.getNextSibling(); - } else if (t == Node.TEXT_NODE) { - String s = e2.getNodeValue().trim(); - if (s.length() == 0) { - e2 = e2.getNextSibling(); - } else { - break; - } - } else { - break; - } - } - - // Same elements, or both null? - if (e1 == e2 || (e1 == null && e2 == null)) { - return true; - } - - // Is one null but not the other? - if ((e1 == null && e2 != null) || (e1 != null && e2 == null)) { - break; // dumpMismatchAndExit - } - - assert e1 != null; - assert e2 != null; - - // Same type? - short t = e1.getNodeType(); - if (t != e2.getNodeType()) { - break; // dumpMismatchAndExit - } - - // Same node name? Must both be null or have the same value. - String s1 = e1.getNodeName(); - String s2 = e2.getNodeName(); - if ( !( (s1 == null && s2 == null) || (s1 != null && s1.equals(s2)) ) ) { - break; // dumpMismatchAndExit - } - - // Same node value? Must both be null or have the same value once whitespace is trimmed. - s1 = e1.getNodeValue(); - s2 = e2.getNodeValue(); - if (s1 != null) { - s1 = s1.trim(); - } - if (s2 != null) { - s2 = s2.trim(); - } - if ( !( (s1 == null && s2 == null) || (s1 != null && s1.equals(s2)) ) ) { - break; // dumpMismatchAndExit - } - + Map<String, String> nsPrefixE = new HashMap<String, String>(); + Map<String, String> nsPrefixA = new HashMap<String, String>(); + String sE = XmlUtils.printElement(expected, nsPrefixE, ""); //$NON-NLS-1$ + String sA = XmlUtils.printElement(actual, nsPrefixA, ""); //$NON-NLS-1$ + if (sE.equals(sA)) { + return true; + } else { if (diff != null) { - // So far e1 and e2 seem pretty much equal. Dump it to the diff. - // We need to print to the diff before dealing with the children or attributes. - // Note: diffOffset + 1 because we want to reserve 2 spaces to write -/+ - diff.append(XmlUtils.dump(e1, diffOffset + 1, - false /*nextSiblings*/, false /*deep*/, keyAttr)); - } - - // Now compare the attributes. When using the w3c.DOM this way, attributes are - // accessible via the Node/Element attributeMap and are not actually exposed - // as ATTR_NODEs in the node list. The downside is that we don't really - // have the proper attribute order but that's not an issue as far as the validity - // of the XML since attribute order should never matter. - List<Attr> a1 = XmlUtils.sortedAttributeList(e1.getAttributes()); - List<Attr> a2 = XmlUtils.sortedAttributeList(e2.getAttributes()); - if (a1.size() > 0 || a2.size() > 0) { - - int count1 = 0; - int count2 = 0; - Map<String, AttrDiff> map = new TreeMap<String, AttrDiff>(); - for (Attr a : a1) { - AttrDiff ad1 = new AttrDiff(a, "--"); //$NON-NLS-1$ - map.put(ad1.mKey, ad1); - count1++; - } - - for (Attr a : a2) { - AttrDiff ad2 = new AttrDiff(a, "++"); //$NON-NLS-1$ - AttrDiff ad1 = map.get(ad2.mKey); - if (ad1 != null) { - ad1.mSide = " "; //$NON-NLS-1$ - count1--; - } else { - map.put(ad2.mKey, ad2); - count2++; - } - } - - if (count1 != 0 || count2 != 0) { - // We found some items not matching in both sets. Dump the result. - if (diff != null) { - for (AttrDiff ad : map.values()) { - diff.append(ad.mSide) - .append(XmlUtils.dump(ad.mAttr, diffOffset, - false /*nextSiblings*/, false /*deep*/, - keyAttr)); - } - } - // Exit without dumping - return false; - } + XmlUtils.printXmlDiff(diff, sE, sA, nsPrefixE, nsPrefixA, NS_URI + ':' + keyAttr); } - - // Compare recursively for elements. - if (t == Node.ELEMENT_NODE && - !compareElements( - e1.getFirstChild(), e2.getFirstChild(), true, - diff, diffOffset + 1, keyAttr)) { - // Exit without dumping since the recursive call take cares of its own diff - return false; - } - - if (nextSiblings) { - e1 = e1.getNextSibling(); - e2 = e2.getNextSibling(); - continue; - } else { - return true; - } - } - - // <INTERCAL COME FROM dumpMismatchAndExit PLEASE> - if (diff != null) { - diff.append("--") - .append(XmlUtils.dump(e1, diffOffset, - false /*nextSiblings*/, false /*deep*/, keyAttr)); - diff.append("++") - .append(XmlUtils.dump(e2, diffOffset, - false /*nextSiblings*/, false /*deep*/, keyAttr)); - } - return false; - } - - private static class AttrDiff { - public final String mKey; - public final Attr mAttr; - public String mSide; - - public AttrDiff(Attr attr, String side) { - mKey = getKey(attr); - mAttr = attr; - mSide = side; - } - - String getKey(Attr attr) { - return String.format("%s=%s", attr.getNodeName(), attr.getNodeValue()); + return false; } } @@ -1521,4 +1348,5 @@ public class ManifestMerger { return new FileAndLine(name, line); } + } diff --git a/manifmerger/src/com/android/manifmerger/XmlUtils.java b/manifmerger/src/com/android/manifmerger/XmlUtils.java index 7e92d55..71aac91 100755 --- a/manifmerger/src/com/android/manifmerger/XmlUtils.java +++ b/manifmerger/src/com/android/manifmerger/XmlUtils.java @@ -39,6 +39,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -499,4 +503,296 @@ class XmlUtils { } }; } + + // ------- + + /** + * Flatten the element to a string. This "pretty prints" the XML tree starting + * from the given node and all its children and attributes. + * <p/> + * The output is designed to be printed using {@link #printXmlDiff}. + * + * @param node The root node to print. + * @param nsPrefix A map that is filled with all the URI=>prefix found. + * The internal string only contains the expanded URIs but this is rather verbose + * so when printing the diff these will be replaced by the prefixes collected here. + * @param prefix A "space" prefix added at the beginning of each line for indentation + * purposes. The diff printer later relies on this to find out the structure. + */ + @NonNull + static String printElement( + @NonNull Node node, + @NonNull Map<String, String> nsPrefix, + @NonNull String prefix) { + StringBuilder sb = new StringBuilder(); + sb.append(prefix).append('<'); + String uri = node.getNamespaceURI(); + if (uri != null) { + sb.append(uri).append(':'); + nsPrefix.put(uri, node.getPrefix()); + } + sb.append(node.getLocalName()); + printAttributes(sb, node, nsPrefix, prefix); + sb.append(">\n"); //$NON-NLS-1$ + printChildren(sb, node.getFirstChild(), true, nsPrefix, prefix + " "); //$NON-NLS-1$ + + sb.append(prefix).append("</"); //$NON-NLS-1$ + if (uri != null) { + sb.append(uri).append(':'); + } + sb.append(node.getLocalName()); + sb.append(">\n"); //$NON-NLS-1$ + + return sb.toString(); + } + + /** + * Flatten several children elements to a string. + * This is an implementation detail for {@link #printElement(Node, Map, String)}. + * <p/> + * If {@code nextSiblings} is false, the string conversion takes only the given + * child element and stops there. + * <p/> + * If {@code nextSiblings} is true, the string conversion also takes _all_ the siblings + * after the given element. The idea is the caller can call this with the first child + * of a parent and get a string showing all the children at the same time. They are + * sorted to avoid the ordering issue. + */ + @NonNull + private static StringBuilder printChildren( + @NonNull StringBuilder sb, + @NonNull Node child, + boolean nextSiblings, + @NonNull Map<String, String> nsPrefix, + @NonNull String prefix) { + ArrayList<String> children = new ArrayList<String>(); + + boolean hasText = false; + for (; child != null; child = child.getNextSibling()) { + short t = child.getNodeType(); + if (nextSiblings && t == Node.TEXT_NODE) { + // We don't typically have meaningful text nodes in an Android manifest. + // If there are, just dump them as-is into the element representation. + // We do trim whitespace and ignore all-whitespace or empty text nodes. + String s = child.getNodeValue().trim(); + if (s.length() > 0) { + sb.append(s); + hasText = true; + } + } else if (t == Node.ELEMENT_NODE) { + children.add(printElement(child, nsPrefix, prefix)); //$NON-NLS-1$ + if (!nextSiblings) { + break; + } + } + } + + if (hasText) { + sb.append('\n'); + } + + if (!children.isEmpty()) { + Collections.sort(children); + for (String s : children) { + sb.append(s); + } + } + + return sb; + } + + /** + * Flatten several attributes to a string using their alphabethical order. + * This is an implementation detail for {@link #printElement(Node, Map, String)}. + */ + @NonNull + private static StringBuilder printAttributes( + @NonNull StringBuilder sb, + @NonNull Node node, + @NonNull Map<String, String> nsPrefix, + @NonNull String prefix) { + ArrayList<String> attrs = new ArrayList<String>(); + + NamedNodeMap attrMap = node.getAttributes(); + if (attrMap != null) { + StringBuilder sb2 = new StringBuilder(); + for (int i = 0; i < attrMap.getLength(); i++) { + Node attr = attrMap.item(i); + if (attr instanceof Attr) { + sb2.setLength(0); + sb2.append('@'); + String uri = attr.getNamespaceURI(); + if (uri != null) { + sb2.append(uri).append(':'); + nsPrefix.put(uri, attr.getPrefix()); + } + sb2.append(attr.getLocalName()); + sb2.append("=\"").append(attr.getNodeValue()).append('\"'); //$NON-NLS-1$ + attrs.add(sb2.toString()); + } + } + } + + Collections.sort(attrs); + + for(String attr : attrs) { + sb.append('\n'); + sb.append(prefix).append(" ").append(attr); //$NON-NLS-1$ + } + return sb; + } + + //------------ + + /** + * Computes a quick diff between two strings generated by + * {@link #printElement(Node, Map, String)}. + * <p/> + * This is a <em>not</em> designed to be a full contextual diff. + * It just stops at the first difference found, printing up to 3 lines of diff + * and backtracking to add prior contextual information to understand the + * structure of the element where the first diff line occured (by printing + * each parent found till the root one as well as printing the attribute + * named by {@code keyAttr}). + * + * @param sb The string builder where to output is written. + * @param expected The expected XML tree (as generated by {@link #printElement}.) + * For best result this would be the "destination" XML we're merging into, + * e.g. the main manifest. + * @param actual The actual XML tree (as generated by {@link #printElement}.) + * For best result this would be the "source" XML we're merging from, + * e.g. a library manifest. + * @param nsPrefixE The map of URI=>prefix for the expected XML tree. + * @param nsPrefixA The map of URI=>prefix for the actual XML tree. + * @param keyAttr An optional attribute *full* name (uri:local name) to always + * insert when writing the contextual lines before a diff line. + * For example when writing an Activity, it helps to always insert + * the "name" attribute since that's the key element to help the user + * identify which node is being dumped. + */ + static void printXmlDiff( + StringBuilder sb, + String expected, + String actual, + Map<String, String> nsPrefixE, + Map<String, String> nsPrefixA, + String keyAttr) { + String[] aE = expected.split("\n"); + String[] aA = actual.split("\n"); + int lE = aE.length; + int lA = aA.length; + int lm = lE < lA ? lA : lE; + boolean eofE = false; + boolean eofA = false; + boolean contextE = true; + boolean contextA = true; + int numDiff = 0; + + StringBuilder sE = new StringBuilder(); + StringBuilder sA = new StringBuilder(); + + outerLoop: for (int i = 0, iE = 0, iA = 0; i < lm; i++) { + if (iE < lE && iA < lA && aE[iE].equals(aA[iA])) { + if (numDiff > 0) { + // If we found a difference, stop now. + break outerLoop; + } + iE++; + iA++; + continue; + } else { + // Try to print some context for each side based on previous lines's space prefix. + if (contextE) { + if (iE > 0) { + String p = diffGetPrefix(aE[iE]); + for (int kE = iE-1; kE >= 0; kE--) { + if (!aE[kE].startsWith(p)) { + sE.insert(0, '\n').insert(0, diffReplaceNs(aE[kE], nsPrefixE)).insert(0, " "); + if (p.length() == 0) { + break; + } + p = diffGetPrefix(aE[kE]); + } else if (aE[kE].contains(keyAttr) || kE == 0) { + sE.insert(0, '\n').insert(0, diffReplaceNs(aE[kE], nsPrefixE)).insert(0, " "); + } + } + } + contextE = false; + } + if (iE >= lE) { + if (!eofE) { + sE.append("--(end reached)\n"); + eofE = true; + } + } else { + sE.append("--").append(diffReplaceNs(aE[iE++], nsPrefixE)).append('\n'); + } + + if (contextA) { + if (iA > 0) { + String p = diffGetPrefix(aA[iA]); + for (int kA = iA-1; kA >= 0; kA--) { + if (!aA[kA].startsWith(p)) { + sA.insert(0, '\n').insert(0, diffReplaceNs(aA[kA], nsPrefixA)).insert(0, " "); + p = diffGetPrefix(aA[kA]); + if (p.length() == 0) { + break; + } + } else if (aA[kA].contains(keyAttr) || kA == 0) { + sA.insert(0, '\n').insert(0, diffReplaceNs(aA[kA], nsPrefixA)).insert(0, " "); + } + } + } + contextA = false; + } + if (iA >= lA) { + if (!eofA) { + sA.append("++(end reached)\n"); + eofA = true; + } + } else { + sA.append("++").append(diffReplaceNs(aA[iA++], nsPrefixA)).append('\n'); + } + + // Dump up to 3 lines of difference + numDiff++; + if (numDiff == 3) { + break outerLoop; + } + } + } + + sb.append(sE); + sb.append(sA); + } + + /** + * Returns all the whitespace at the beginning of a string. + * Implementation details for {@link #printXmlDiff} used to find the "parent" + * element and include it in the context of the diff. + */ + private static String diffGetPrefix(String str) { + int pos = 0; + int len = str.length(); + while (pos < len && str.charAt(pos) == ' ') { + pos++; + } + return str.substring(0, pos); + } + + /** + * Simplifies a diff line by replacing NS URIs by their prefix. + * Implementation details for {@link #printXmlDiff}. + */ + private static String diffReplaceNs(String str, Map<String, String> nsPrefix) { + for (Entry<String, String> entry : nsPrefix.entrySet()) { + String uri = entry.getKey(); + String prefix = entry.getValue(); + if (prefix != null && str.contains(uri)) { + str = str.replaceAll(Pattern.quote(uri), Matcher.quoteReplacement(prefix)); + } + } + return str; + } + } diff --git a/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTest.java b/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTest.java index 1482792..564fc6d 100755 --- a/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTest.java +++ b/manifmerger/tests/src/com/android/manifmerger/ManifestMergerTest.java @@ -145,4 +145,8 @@ public class ManifestMergerTest extends ManifestMergerTestCase { public void test56_support_gltext_warning() throws Exception { processTestFiles(); } + + public void test60_merge_order() throws Exception { + processTestFiles(); + } } diff --git a/manifmerger/tests/src/com/android/manifmerger/data/11_activity_dup.xml b/manifmerger/tests/src/com/android/manifmerger/data/11_activity_dup.xml index 5ba6688..ef163b0 100755 --- a/manifmerger/tests/src/com/android/manifmerger/data/11_activity_dup.xml +++ b/manifmerger/tests/src/com/android/manifmerger/data/11_activity_dup.xml @@ -366,22 +366,41 @@ </manifest> + @errors E [ManifestMergerTest0_main.xml:32, ManifestMergerTest1_lib1_widget.xml:16] Trying to merge incompatible /manifest/application/activity[@name=com.example.WidgetConfigurationUI] element: - <activity android:name=com.example.WidgetConfigurationUI> --- <intent-filter> -++ (end reached) + <activity + @android:name="com.example.WidgetConfigurationUI" +--</activity> +--(end reached) + <activity + @android:name="com.example.WidgetConfigurationUI" +++ <intent-filter> +++ <action +++ @android:name="android.appwidget.action.APPWIDGET_CONFIGURE"> E [ManifestMergerTest0_main.xml:38, ManifestMergerTest2_lib2_activity.xml:6] Trying to merge incompatible /manifest/application/activity[@name=com.example.LibActivity] element: - <activity android:name=com.example.LibActivity> - @android:icon = @drawable/lib_activity_icon - @android:label = @string/lib_activity_name - @android:name = com.example.LibActivity --- @android:theme = @style/Lib.Theme + <activity +-- @android:name="com.example.LibActivity"> +-- <intent-filter> +-- <action + <activity +++ @android:name="com.example.LibActivity" +++ @android:theme="@style/Lib.Theme"> +++ <intent-filter> E [ManifestMergerTest0_main.xml, ManifestMergerTest3_lib3_alias.xml:19] Trying to merge incompatible /manifest/application/activity[@name=com.example.LibActivity2] element: - <activity android:name=com.example.LibActivity2> - <intent-filter> - <action android:name=android.intent.action.MAIN> - <category android:name=android.intent.category.LAUNCHER> --- <category android:name=android.intent.category.MOARCATZPLZ> -++ (end reached) + <activity + @android:name="com.example.LibActivity2" + @android:name="android.intent.action.MAIN"> + @android:name="android.intent.category.LAUNCHER"> +-- </intent-filter> +--</activity> +--(end reached) + <activity + @android:name="com.example.LibActivity2" + <intent-filter> + @android:name="android.intent.action.MAIN"> + @android:name="android.intent.category.LAUNCHER"> +++ <category +++ @android:name="android.intent.category.MOARCATZPLZ"> +++ </category> diff --git a/manifmerger/tests/src/com/android/manifmerger/data/12_alias_dup.xml b/manifmerger/tests/src/com/android/manifmerger/data/12_alias_dup.xml index 696965f..7b5aed3 100755 --- a/manifmerger/tests/src/com/android/manifmerger/data/12_alias_dup.xml +++ b/manifmerger/tests/src/com/android/manifmerger/data/12_alias_dup.xml @@ -192,14 +192,20 @@ P [ManifestMergerTest0_main.xml:6, ManifestMergerTest1_lib1.xml:6] Skipping identical /manifest/application/activity-alias[@name=com.example.alias.MyActivity1] element. E [ManifestMergerTest0_main.xml:13, ManifestMergerTest1_lib1.xml:14] Trying to merge incompatible /manifest/application/activity-alias[@name=com.example.alias.MyActivity2] element: - <activity-alias android:name=com.example.alias.MyActivity2> -++ @android:icon = @drawable/alias_icon2 -++ @android:label = @string/alias_name2 - @android:name = com.example.alias.MyActivity2 - @android:targetActivity = com.example.MainActivity2 + <activity-alias +-- @android:icon="@drawable/alias_icon2" +-- @android:label="@string/alias_name2" +-- @android:name="com.example.alias.MyActivity2" + <activity-alias +++ @android:name="com.example.alias.MyActivity2" +++ @android:targetActivity="com.example.MainActivity2"> +++</activity-alias> E [ManifestMergerTest0_main.xml, ManifestMergerTest2_lib2.xml:6] Trying to merge incompatible /manifest/application/activity-alias[@name=com.example.alias.MyActivity3] element: - <activity-alias android:name=com.example.alias.MyActivity3> - @android:icon = @drawable/alias_icon3 - @android:label = @string/alias_name3 - @android:name = com.example.alias.MyActivity3 -++ @android:targetActivity = com.example.MainActivity3 + <activity-alias +-- @android:name="com.example.alias.MyActivity3" +-- @android:targetActivity="com.example.MainActivity3"> +-- <intent-filter> + <activity-alias +++ @android:name="com.example.alias.MyActivity3"> +++ <intent-filter> +++ <category diff --git a/manifmerger/tests/src/com/android/manifmerger/data/13_service_dup.xml b/manifmerger/tests/src/com/android/manifmerger/data/13_service_dup.xml index 36d7e24..4c257fa 100755 --- a/manifmerger/tests/src/com/android/manifmerger/data/13_service_dup.xml +++ b/manifmerger/tests/src/com/android/manifmerger/data/13_service_dup.xml @@ -146,10 +146,22 @@ P [ManifestMergerTest0_main.xml:6, ManifestMergerTest1_lib1.xml:6] Skipping identical /manifest/application/service[@name=com.example.AppService1] element. E [ManifestMergerTest0_main.xml:8, ManifestMergerTest1_lib1.xml:9] Trying to merge incompatible /manifest/application/service[@name=com.example.AppService2] element: - <service android:name=com.example.AppService2> --- <intent-filter> -++ (end reached) + <service + @android:name="com.example.AppService2"> +--</service> +--(end reached) + <service + @android:name="com.example.AppService2"> +++ <intent-filter> +++ <action +++ @android:name="android.intent.action.MAIN"> E [ManifestMergerTest0_main.xml, ManifestMergerTest2_lib2.xml:6] Trying to merge incompatible /manifest/application/service[@name=com.example.AppService3] element: - <service android:name=com.example.AppService3> --- (end reached) -++ <intent-filter> + <service + @android:name="com.example.AppService3"> +-- <intent-filter> +-- <action +-- @android:name="android.intent.action.MAIN"> + <service + @android:name="com.example.AppService3"> +++</service> +++(end reached) diff --git a/manifmerger/tests/src/com/android/manifmerger/data/14_receiver_dup.xml b/manifmerger/tests/src/com/android/manifmerger/data/14_receiver_dup.xml index a2547af..777ba22 100755 --- a/manifmerger/tests/src/com/android/manifmerger/data/14_receiver_dup.xml +++ b/manifmerger/tests/src/com/android/manifmerger/data/14_receiver_dup.xml @@ -165,12 +165,22 @@ P [ManifestMergerTest0_main.xml:6, ManifestMergerTest1_lib1.xml:6] Skipping identical /manifest/application/receiver[@name=com.example.AppReceiver1] element. E [ManifestMergerTest0_main.xml:12, ManifestMergerTest1_lib1.xml:13] Trying to merge incompatible /manifest/application/receiver[@name=com.example.AppReceiver2] element: - <receiver android:name=com.example.AppReceiver2> -++ @android:icon = @drawable/app_icon - @android:name = com.example.AppReceiver2 + <receiver +-- @android:icon="@drawable/app_icon" +-- @android:name="com.example.AppReceiver2"> +-- <intent-filter> + <receiver +++ @android:name="com.example.AppReceiver2"> +++</receiver> +++(end reached) E [ManifestMergerTest0_main.xml, ManifestMergerTest2_lib2.xml:6] Trying to merge incompatible /manifest/application/receiver[@name=com.example.AppReceiver3] element: - <receiver android:name=com.example.AppReceiver3> - <intent-filter> - <action android:name=com.example.action.ACTION_CUSTOM> --- @android:name = com.example.action.ACTION_CUSTOM -++ @android:name = com.example.action.ACTION_CUSTOM1 + <receiver + @android:name="com.example.AppReceiver3"> + <intent-filter> + <action +-- @android:name="com.example.action.ACTION_CUSTOM1"> + <receiver + @android:name="com.example.AppReceiver3"> + <intent-filter> + <action +++ @android:name="com.example.action.ACTION_CUSTOM"> diff --git a/manifmerger/tests/src/com/android/manifmerger/data/15_provider_dup.xml b/manifmerger/tests/src/com/android/manifmerger/data/15_provider_dup.xml index 7938c1e..bd0c8fe 100755 --- a/manifmerger/tests/src/com/android/manifmerger/data/15_provider_dup.xml +++ b/manifmerger/tests/src/com/android/manifmerger/data/15_provider_dup.xml @@ -134,12 +134,20 @@ P [ManifestMergerTest0_main.xml:6, ManifestMergerTest1_lib1.xml:6] Skipping identical /manifest/application/provider[@name=com.example.Provider1] element. E [ManifestMergerTest0_main.xml:8, ManifestMergerTest1_lib1.xml:9] Trying to merge incompatible /manifest/application/provider[@name=com.example.Provider2] element: - <provider android:name=com.example.Provider2> --- @android:authorities = com.example.android.apis.app.thingy2 --- @android:enabled = @bool/someConditionalValue2 - @android:name = com.example.Provider2 + <provider +-- @android:name="com.example.Provider2"> +--</provider> +--(end reached) + <provider +++ @android:authorities="com.example.android.apis.app.thingy2" +++ @android:enabled="@bool/someConditionalValue2" +++ @android:name="com.example.Provider2"> E [ManifestMergerTest0_main.xml, ManifestMergerTest2_lib2.xml:6] Trying to merge incompatible /manifest/application/provider[@name=com.example.Provider3] element: - <provider android:name=com.example.Provider3> - @android:authorities = com.example.android.apis.app.thingy3 -++ @android:enabled = @bool/someConditionalValue - @android:name = com.example.Provider3 + <provider +-- @android:enabled="@bool/someConditionalValue" +-- @android:name="com.example.Provider3"> +--</provider> + <provider +++ @android:name="com.example.Provider3"> +++</provider> +++(end reached) diff --git a/manifmerger/tests/src/com/android/manifmerger/data/26_permission_dup.xml b/manifmerger/tests/src/com/android/manifmerger/data/26_permission_dup.xml index 3862249..bd9a4f1 100755 --- a/manifmerger/tests/src/com/android/manifmerger/data/26_permission_dup.xml +++ b/manifmerger/tests/src/com/android/manifmerger/data/26_permission_dup.xml @@ -269,39 +269,46 @@ @errors E [ManifestMergerTest0_main.xml:12, ManifestMergerTest1_lib1.xml:4] Trying to merge incompatible /manifest/permission[@name=com.example.DangerWillRobinson] element: - <permission android:name=com.example.DangerWillRobinson> --- @android:description = Different description here -++ @android:description = Insert boring description here --- @android:icon = @drawable/not_the_same_icon -++ @android:icon = @drawable/robot - @android:label = Danger, Will Robinson! - @android:name = com.example.DangerWillRobinson - @android:permissionGroup = com.example.MasterControlPermission - @android:protectionLevel = dangerous + <permission +-- @android:description="Insert boring description here" +-- @android:icon="@drawable/robot" + <permission +++ @android:description="Different description here" +++ @android:icon="@drawable/not_the_same_icon" E [ManifestMergerTest0_main.xml:14, ManifestMergerTest1_lib1.xml:8] Trying to merge incompatible /manifest/permission[@name=com.example.WhatWereYouThinking] element: - <permission android:name=com.example.WhatWereYouThinking> - @android:name = com.example.WhatWereYouThinking - @android:permissionGroup = com.example.MasterControlPermission --- @android:protectionLevel = normal -++ @android:protectionLevel = signatureOrSystem + <permission + @android:name="com.example.WhatWereYouThinking" +-- @android:protectionLevel="signatureOrSystem"> + <permission + @android:name="com.example.WhatWereYouThinking" +++ @android:protectionLevel="normal"> E [ManifestMergerTest0_main.xml:16, ManifestMergerTest1_lib1.xml:5] Trying to merge incompatible /manifest/permission-group[@name=com.example.MasterControlPermission] element: - <permission-group android:name=com.example.MasterControlPermission> - @android:description = Nobody expects... -++ @android:icon = @drawable/ignored_icon - @android:label = the Spanish Inquisition - @android:name = com.example.MasterControlPermission + <permission-group +-- @android:icon="@drawable/ignored_icon" +-- @android:label="the Spanish Inquisition" +-- @android:name="com.example.MasterControlPermission"> + <permission-group +++ @android:label="the Spanish Inquisition" +++ @android:name="com.example.MasterControlPermission"> +++</permission-group> E [ManifestMergerTest0_main.xml:18, ManifestMergerTest1_lib1.xml:6] Trying to merge incompatible /manifest/permission-tree[@name=com.example.PermTree] element: - <permission-tree android:name=com.example.PermTree> -++ @android:label = This is not a label --- @android:label = This is not the same label - @android:name = com.example.PermTree + <permission-tree +-- @android:label="This is not a label" + <permission-tree +++ @android:label="This is not the same label" E [ManifestMergerTest0_main.xml, ManifestMergerTest2_lib2.xml:6] Trying to merge incompatible /manifest/permission[@name=com.example.Permission1] element: - <permission android:name=com.example.Permission1> - @android:name = com.example.Permission1 - @android:permissionGroup = com.example.Permission1 -++ @android:protectionLevel = normal --- @android:protectionLevel = system + <permission + @android:name="com.example.Permission1" +-- @android:protectionLevel="normal"> + <permission + @android:name="com.example.Permission1" +++ @android:protectionLevel="system"> E [ManifestMergerTest0_main.xml, ManifestMergerTest2_lib2.xml:7] Trying to merge incompatible /manifest/permission-tree[@name=com.example.PermTree1] element: - <permission-tree android:name=com.example.PermTree1> --- @android:description = Extra description - @android:name = com.example.PermTree1 + <permission-tree +-- @android:name="com.example.PermTree1"> +--</permission-tree> +--(end reached) + <permission-tree +++ @android:description="Extra description" +++ @android:name="com.example.PermTree1"> +++</permission-tree> diff --git a/manifmerger/tests/src/com/android/manifmerger/data/60_merge_order.xml b/manifmerger/tests/src/com/android/manifmerger/data/60_merge_order.xml new file mode 100755 index 0000000..e820ecb --- /dev/null +++ b/manifmerger/tests/src/com/android/manifmerger/data/60_merge_order.xml @@ -0,0 +1,317 @@ +# +# Test: +# - When activity / activity-alias / service / receiver / provider are merged, +# we do a comparison to check whether the elements are already present in the +# main manifest. +# - What this checks is that the order of the elements or attributes within +# the elements should not matter. +# + +@main + +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.app1"> + + <application + android:label="@string/app_name" + android:icon="@drawable/app_icon" + android:backupAgent="com.example.app.BackupAgentClass" + android:restoreAnyVersion="true" + android:allowBackup="true" + android:killAfterRestore="true" + android:name="com.example.TheApp" > + + <activity + android:name="com.example.Activity1" + android:label="@string/activity_name" + android:icon="@drawable/activity_icon" + android:theme="@style/Some.Theme"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + <meta-data + android:name="metaName" + android:value="metaValue" + android:resource="@color/someColor" /> + </activity> + + <!-- Receiver --> + <receiver + android:name="com.example.AppReceiver" + android:icon="@drawable/app_icon"> + <intent-filter> + <action android:name="android.intent.action.BOOT_COMPLETED" /> + </intent-filter> + <intent-filter> + <action android:name="com.example.intent.action.DO_THIS" /> + </intent-filter> + <intent-filter> + <action android:name="com.example.intent.action.DO_THAT" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.TIMEZONE_CHANGED" /> + <action android:name="android.intent.action.TIME_SET" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.PHONE_STATE"/> + </intent-filter> + </receiver> + + <activity + android:name="com.example.LibActivity" + android:label="@string/lib_activity_name" + android:icon="@drawable/lib_activity_icon" + android:theme="@style/Lib.Theme"> + + <!-- When comparing duplicate elements, whitespace and comments are ignored. --> + + <intent-filter> + <action android:name="com.example.IN_APP_NOTIFY" /> + <action android:name="com.example.RESPONSE_CODE" /> + <action android:name="com.example.PURCHASE_STATE_CHANGED" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + <meta-data + android:name="metaName2" + android:value="metaValue2" + android:resource="@color/someColor2" + /> + </activity> + </application> +</manifest> + + +@lib1 + +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.lib1"> + + <!-- Redefine the same elements as in the main manifest except it changes + the attribute order and the the inner elements order. --> + <application + android:name="com.example.TheApp" + android:icon="@drawable/app_icon" + android:label="@string/app_name" + android:allowBackup="true" + android:killAfterRestore="true" + android:restoreAnyVersion="true" + android:backupAgent="com.example.app.BackupAgentClass" + > + + <!-- Receiver --> + <receiver + android:icon="@drawable/app_icon" + android:name="com.example.AppReceiver" + > + <intent-filter> + <action android:name="android.intent.action.TIME_SET" /> + <action android:name="android.intent.action.TIMEZONE_CHANGED" /> + </intent-filter> + <intent-filter> + <action android:name="com.example.intent.action.DO_THIS" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.PHONE_STATE"/> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.BOOT_COMPLETED" /> + </intent-filter> + <intent-filter> + <action android:name="com.example.intent.action.DO_THAT" /> + </intent-filter> + </receiver> + + <activity + android:theme="@style/Lib.Theme" + android:name="com.example.LibActivity" + android:icon="@drawable/lib_activity_icon" + android:label="@string/lib_activity_name" + > + <!-- When comparing duplicate elements, whitespace and comments are ignored. --> + <intent-filter> + <category android:name="android.intent.category.LAUNCHER" /> + <action android:name="android.intent.action.MAIN" /> + </intent-filter> + <meta-data + android:resource="@color/someColor2" + android:value="metaValue2" + android:name="metaName2"> + </meta-data> + <intent-filter> + <action android:name="com.example.IN_APP_NOTIFY" /> + <action android:name="com.example.PURCHASE_STATE_CHANGED" /> + <action android:name="com.example.RESPONSE_CODE" /> + </intent-filter> + </activity> + + <activity + android:icon="@drawable/activity_icon" + android:label="@string/activity_name" + android:name="com.example.Activity1" + android:theme="@style/Some.Theme"> + <meta-data + android:value="metaValue" + android:name="metaName" + android:resource="@color/someColor" /> + <intent-filter> + <category android:name="android.intent.category.LAUNCHER" /> + <action android:name="android.intent.action.MAIN" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> + +</manifest> + + +@lib2 + +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.lib2"> + + <application + android:label="@string/app_name" + android:icon="@drawable/app_icon" + android:backupAgent="com.example.app.BackupAgentClass" + android:restoreAnyVersion="true" + android:allowBackup="true" + android:killAfterRestore="true" + android:name="com.example.TheApp" > + + <!-- The whitespace and alignment is also drastically different here and has + no impact whatsoever on the content's comparison. + Some empty elements have been 'uncollapsed' with their closing element separated. --> + <activity android:label="@string/activity_name" android:icon="@drawable/activity_icon" android:theme="@style/Some.Theme" android:name="com.example.Activity1"> + <intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter> + <intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter> + <meta-data android:value="metaValue" android:resource="@color/someColor" android:name="metaName" /> + </activity> + <activity android:label="@string/lib_activity_name" android:icon="@drawable/lib_activity_icon" android:name="com.example.LibActivity" android:theme="@style/Lib.Theme"><intent-filter> + <action android:name="com.example.IN_APP_NOTIFY" /> <action android:name="com.example.RESPONSE_CODE" /> <action android:name="com.example.PURCHASE_STATE_CHANGED" /> + </intent-filter> + <intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + <meta-data android:name="metaName2" android:value="metaValue2" android:resource="@color/someColor2" + /> + </activity> + + <!-- Receiver --> + <receiver android:icon="@drawable/app_icon" android:name="com.example.AppReceiver" > + <intent-filter><action android:name="android.intent.action.TIME_SET"></action> + <action android:name="android.intent.action.TIMEZONE_CHANGED" /></intent-filter> + <intent-filter><action android:name="android.intent.action.PHONE_STATE" > + </action></intent-filter> + <intent-filter><action android:name="android.intent.action.BOOT_COMPLETED" /> + </intent-filter> + <intent-filter><action android:name="com.example.intent.action.DO_THIS" /></intent-filter> + <intent-filter><action android:name="com.example.intent.action.DO_THAT" /></intent-filter> + </receiver> + </application> +</manifest> + + + +@result + +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.app1"> + + <application + android:label="@string/app_name" + android:icon="@drawable/app_icon" + android:backupAgent="com.example.app.BackupAgentClass" + android:restoreAnyVersion="true" + android:allowBackup="true" + android:killAfterRestore="true" + android:name="com.example.TheApp" > + + <activity + android:name="com.example.Activity1" + android:label="@string/activity_name" + android:icon="@drawable/activity_icon" + android:theme="@style/Some.Theme"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + <meta-data + android:name="metaName" + android:value="metaValue" + android:resource="@color/someColor" /> + </activity> + + <!-- Receiver --> + <receiver + android:name="com.example.AppReceiver" + android:icon="@drawable/app_icon"> + <intent-filter> + <action android:name="android.intent.action.BOOT_COMPLETED" /> + </intent-filter> + <intent-filter> + <action android:name="com.example.intent.action.DO_THIS" /> + </intent-filter> + <intent-filter> + <action android:name="com.example.intent.action.DO_THAT" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.TIMEZONE_CHANGED" /> + <action android:name="android.intent.action.TIME_SET" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.PHONE_STATE"/> + </intent-filter> + </receiver> + + <activity + android:name="com.example.LibActivity" + android:label="@string/lib_activity_name" + android:icon="@drawable/lib_activity_icon" + android:theme="@style/Lib.Theme"> + + <!-- When comparing duplicate elements, whitespace and comments are ignored. --> + + <intent-filter> + <action android:name="com.example.IN_APP_NOTIFY" /> + <action android:name="com.example.RESPONSE_CODE" /> + <action android:name="com.example.PURCHASE_STATE_CHANGED" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + <meta-data + android:name="metaName2" + android:value="metaValue2" + android:resource="@color/someColor2" + /> + </activity> + </application> +</manifest> + +@errors + +P [ManifestMergerTest0_main.xml:37, ManifestMergerTest1_lib1.xml:26] Skipping identical /manifest/application/activity[@name=com.example.LibActivity] element. +P [ManifestMergerTest0_main.xml:5, ManifestMergerTest1_lib1.xml:41] Skipping identical /manifest/application/activity[@name=com.example.Activity1] element. +P [ManifestMergerTest0_main.xml:18, ManifestMergerTest1_lib1.xml:7] Skipping identical /manifest/application/receiver[@name=com.example.AppReceiver] element. +P [ManifestMergerTest0_main.xml:5, ManifestMergerTest2_lib2.xml:6] Skipping identical /manifest/application/activity[@name=com.example.Activity1] element. +P [ManifestMergerTest0_main.xml:37, ManifestMergerTest2_lib2.xml:11] Skipping identical /manifest/application/activity[@name=com.example.LibActivity] element. +P [ManifestMergerTest0_main.xml:18, ManifestMergerTest2_lib2.xml:20] Skipping identical /manifest/application/receiver[@name=com.example.AppReceiver] element. |