diff options
author | 2018-04-03 08:35:51 -0700 | |
---|---|---|
committer | 2018-04-03 08:37:36 -0700 | |
commit | 5437b080f3ba8b51e502027cb2df15ec80cd2fbf (patch) | |
tree | e31160a5783e484b3098a0682464ae79876d02b8 | |
parent | a8023b796db7d05e329d0eb9a51cb4ce8222e4a0 (diff) |
Automated rollback of commit e8bed799d59526541afa2a0e9ef5d4c49e3ba390.
*** Reason for rollback ***
Rolling forward with improved handling and testing for Styleables, and correct package management.
*** Original change description ***
Automated rollback of commit a76f7db51a90cc2e35c1d66782056c310729eef0.
*** Reason for rollback ***
Breaks Kix.
*** Original change description ***
Modify the .flat decompilation to account for multiple configurations by converting the aapt2 proto ConfigValue to a FolderConfiguration.
Adds new aapt2 compiled deserialization test.
RELNOTES: None
PiperOrigin-RevId: 191444658
5 files changed, 424 insertions, 79 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidCompiledDataDeserializer.java b/src/tools/android/java/com/google/devtools/build/android/AndroidCompiledDataDeserializer.java index fb4d93dcc6..ec29c67fb7 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AndroidCompiledDataDeserializer.java +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidCompiledDataDeserializer.java @@ -13,8 +13,19 @@ // limitations under the License. package com.google.devtools.build.android; +import static java.util.stream.Collectors.toList; + import android.aapt.pb.internal.ResourcesInternal.CompiledFile; import com.android.SdkConstants; +import com.android.aapt.ConfigurationOuterClass.Configuration; +import com.android.aapt.ConfigurationOuterClass.Configuration.KeysHidden; +import com.android.aapt.ConfigurationOuterClass.Configuration.NavHidden; +import com.android.aapt.ConfigurationOuterClass.Configuration.Orientation; +import com.android.aapt.ConfigurationOuterClass.Configuration.ScreenLayoutLong; +import com.android.aapt.ConfigurationOuterClass.Configuration.ScreenLayoutSize; +import com.android.aapt.ConfigurationOuterClass.Configuration.Touchscreen; +import com.android.aapt.ConfigurationOuterClass.Configuration.UiModeNight; +import com.android.aapt.ConfigurationOuterClass.Configuration.UiModeType; import com.android.aapt.Resources; import com.android.aapt.Resources.ConfigValue; import com.android.aapt.Resources.Package; @@ -22,9 +33,47 @@ import com.android.aapt.Resources.ResourceTable; import com.android.aapt.Resources.Type; import com.android.aapt.Resources.Value; import com.android.aapt.Resources.Visibility.Level; +import com.android.ide.common.resources.configuration.CountryCodeQualifier; +import com.android.ide.common.resources.configuration.DensityQualifier; +import com.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.ide.common.resources.configuration.KeyboardStateQualifier; +import com.android.ide.common.resources.configuration.LayoutDirectionQualifier; +import com.android.ide.common.resources.configuration.LocaleQualifier; +import com.android.ide.common.resources.configuration.NavigationMethodQualifier; +import com.android.ide.common.resources.configuration.NavigationStateQualifier; +import com.android.ide.common.resources.configuration.NetworkCodeQualifier; +import com.android.ide.common.resources.configuration.NightModeQualifier; +import com.android.ide.common.resources.configuration.ResourceQualifier; +import com.android.ide.common.resources.configuration.ScreenDimensionQualifier; +import com.android.ide.common.resources.configuration.ScreenHeightQualifier; +import com.android.ide.common.resources.configuration.ScreenOrientationQualifier; +import com.android.ide.common.resources.configuration.ScreenRatioQualifier; +import com.android.ide.common.resources.configuration.ScreenRoundQualifier; +import com.android.ide.common.resources.configuration.ScreenSizeQualifier; +import com.android.ide.common.resources.configuration.ScreenWidthQualifier; +import com.android.ide.common.resources.configuration.SmallestScreenWidthQualifier; +import com.android.ide.common.resources.configuration.TextInputMethodQualifier; +import com.android.ide.common.resources.configuration.TouchScreenQualifier; +import com.android.ide.common.resources.configuration.UiModeQualifier; +import com.android.ide.common.resources.configuration.VersionQualifier; +import com.android.resources.Density; +import com.android.resources.Keyboard; +import com.android.resources.KeyboardState; +import com.android.resources.LayoutDirection; +import com.android.resources.Navigation; +import com.android.resources.NavigationState; +import com.android.resources.NightMode; import com.android.resources.ResourceType; +import com.android.resources.ScreenOrientation; +import com.android.resources.ScreenRatio; +import com.android.resources.ScreenRound; +import com.android.resources.ScreenSize; +import com.android.resources.TouchScreen; +import com.android.resources.UiMode; import com.google.common.base.Preconditions; import com.google.common.base.Stopwatch; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.io.LittleEndianDataInputStream; import com.google.devtools.build.android.FullyQualifiedName.Factory; @@ -40,14 +89,14 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; -import java.util.Map.Entry; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import java.util.zip.ZipEntry; @@ -58,6 +107,117 @@ public class AndroidCompiledDataDeserializer implements AndroidDataDeserializer private static final Logger logger = Logger.getLogger(AndroidCompiledDataDeserializer.class.getName()); + static final ImmutableMap<Configuration.LayoutDirection, LayoutDirection> LAYOUT_DIRECTION_MAP = + ImmutableMap.of( + Configuration.LayoutDirection.LAYOUT_DIRECTION_LTR, + LayoutDirection.LTR, + Configuration.LayoutDirection.LAYOUT_DIRECTION_RTL, + LayoutDirection.RTL); + + static final ImmutableMap<Configuration.ScreenLayoutSize, ScreenSize> LAYOUT_SIZE_MAP = + ImmutableMap.of( + ScreenLayoutSize.SCREEN_LAYOUT_SIZE_SMALL, + ScreenSize.SMALL, + ScreenLayoutSize.SCREEN_LAYOUT_SIZE_NORMAL, + ScreenSize.NORMAL, + ScreenLayoutSize.SCREEN_LAYOUT_SIZE_LARGE, + ScreenSize.LARGE, + ScreenLayoutSize.SCREEN_LAYOUT_SIZE_XLARGE, + ScreenSize.XLARGE); + + static final ImmutableMap<Configuration.ScreenLayoutLong, ScreenRatio> SCREEN_LONG_MAP = + ImmutableMap.of( + ScreenLayoutLong.SCREEN_LAYOUT_LONG_LONG, + ScreenRatio.LONG, + ScreenLayoutLong.SCREEN_LAYOUT_LONG_NOTLONG, + ScreenRatio.NOTLONG); + + static final ImmutableMap<Configuration.ScreenRound, ScreenRound> SCREEN_ROUND_MAP = + ImmutableMap.of( + Configuration.ScreenRound.SCREEN_ROUND_ROUND, ScreenRound.ROUND, + Configuration.ScreenRound.SCREEN_ROUND_NOTROUND, ScreenRound.NOTROUND); + + private static final ImmutableMap<Configuration.Orientation, ScreenOrientation> + SCREEN_ORIENTATION_MAP = + ImmutableMap.of( + Orientation.ORIENTATION_LAND, ScreenOrientation.LANDSCAPE, + Orientation.ORIENTATION_PORT, ScreenOrientation.PORTRAIT, + Orientation.ORIENTATION_SQUARE, ScreenOrientation.SQUARE); + + private static final ImmutableMap<UiModeType, UiMode> SCREEN_UI_MODE = + ImmutableMap.<UiModeType, UiMode>builder() + .put(UiModeType.UI_MODE_TYPE_APPLIANCE, UiMode.APPLIANCE) + .put(UiModeType.UI_MODE_TYPE_CAR, UiMode.CAR) + .put(UiModeType.UI_MODE_TYPE_DESK, UiMode.DESK) + .put(UiModeType.UI_MODE_TYPE_NORMAL, UiMode.NORMAL) + .put(UiModeType.UI_MODE_TYPE_TELEVISION, UiMode.TELEVISION) + .put(UiModeType.UI_MODE_TYPE_VRHEADSET, UiMode.NORMAL) + .put(UiModeType.UI_MODE_TYPE_WATCH, UiMode.WATCH) + .build(); + + static final ImmutableMap<Configuration.UiModeNight, NightMode> NIGHT_MODE_MAP = + ImmutableMap.of( + UiModeNight.UI_MODE_NIGHT_NIGHT, NightMode.NIGHT, + UiModeNight.UI_MODE_NIGHT_NOTNIGHT, NightMode.NOTNIGHT); + + static final ImmutableMap<Configuration.KeysHidden, KeyboardState> KEYBOARD_STATE_MAP = + ImmutableMap.of( + KeysHidden.KEYS_HIDDEN_KEYSEXPOSED, + KeyboardState.EXPOSED, + KeysHidden.KEYS_HIDDEN_KEYSSOFT, + KeyboardState.SOFT, + KeysHidden.KEYS_HIDDEN_KEYSHIDDEN, + KeyboardState.HIDDEN); + + static final ImmutableMap<Configuration.Touchscreen, TouchScreen> TOUCH_TYPE_MAP = + ImmutableMap.of( + Touchscreen.TOUCHSCREEN_FINGER, + TouchScreen.FINGER, + Touchscreen.TOUCHSCREEN_NOTOUCH, + TouchScreen.NOTOUCH, + Touchscreen.TOUCHSCREEN_STYLUS, + TouchScreen.STYLUS); + + static final ImmutableMap<Configuration.Keyboard, Keyboard> KEYBOARD_MAP = + ImmutableMap.of( + Configuration.Keyboard.KEYBOARD_NOKEYS, + Keyboard.NOKEY, + Configuration.Keyboard.KEYBOARD_QWERTY, + Keyboard.QWERTY, + Configuration.Keyboard.KEYBOARD_TWELVEKEY, + Keyboard.TWELVEKEY); + + static final ImmutableMap<Configuration.NavHidden, NavigationState> NAV_STATE_MAP = + ImmutableMap.of( + NavHidden.NAV_HIDDEN_NAVHIDDEN, + NavigationState.HIDDEN, + NavHidden.NAV_HIDDEN_NAVEXPOSED, + NavigationState.EXPOSED); + + static final ImmutableMap<Configuration.Navigation, Navigation> NAVIGATION_MAP = + ImmutableMap.of( + Configuration.Navigation.NAVIGATION_DPAD, + Navigation.DPAD, + Configuration.Navigation.NAVIGATION_NONAV, + Navigation.NONAV, + Configuration.Navigation.NAVIGATION_TRACKBALL, + Navigation.TRACKBALL, + Configuration.Navigation.NAVIGATION_WHEEL, + Navigation.WHEEL); + + static final ImmutableMap<Integer, Density> DENSITY_MAP = + ImmutableMap.<Integer, Density>builder() + .put(0xfffe, Density.ANYDPI) + .put(0xffff, Density.NODPI) + .put(120, Density.LOW) + .put(160, Density.MEDIUM) + .put(213, Density.TV) + .put(240, Density.HIGH) + .put(320, Density.XHIGH) + .put(480, Density.XXHIGH) + .put(640, Density.XXXHIGH) + .build(); + private final ImmutableSet<String> filteredResources; /** @@ -78,9 +238,8 @@ public class AndroidCompiledDataDeserializer implements AndroidDataDeserializer } private void readResourceTable( - LittleEndianDataInputStream resourceTableStream, - KeyValueConsumers consumers, - Factory fqnFactory) throws IOException { + LittleEndianDataInputStream resourceTableStream, KeyValueConsumers consumers) + throws IOException { long alignedSize = resourceTableStream.readLong(); Preconditions.checkArgument(alignedSize <= Integer.MAX_VALUE); @@ -91,66 +250,218 @@ public class AndroidCompiledDataDeserializer implements AndroidDataDeserializer List<String> sourcePool = decodeSourcePool(resourceTable.getSourcePool().getData().toByteArray()); - Map<String, Entry<FullyQualifiedName, Boolean>> fullyQualifiedNames = new HashMap<>(); + Map<String, Boolean> qualifiedReferenceInlineStatus = new HashMap<>(); for (int i = resourceTable.getPackageCount() - 1; i >= 0; i--) { Package resourceTablePackage = resourceTable.getPackage(i); - String packageName = ""; - if (!resourceTablePackage.getPackageName().isEmpty()) { - packageName = resourceTablePackage.getPackageName() + ":"; - } + String packageName = resourceTablePackage.getPackageName(); for (Type resourceFormatType : resourceTablePackage.getTypeList()) { ResourceType resourceType = ResourceType.getEnum(resourceFormatType.getName()); for (Resources.Entry resource : resourceFormatType.getEntryList()) { - String resourceName = packageName + resource.getName(); - - FullyQualifiedName fqn = fqnFactory.create(resourceType, resourceName); - fullyQualifiedNames.put( - String.format("%s%s/%s", packageName, resourceType, resource.getName()), - new SimpleEntry<FullyQualifiedName, Boolean>(fqn, packageName.isEmpty())); - - List<ConfigValue> configValues = resource.getConfigValueList(); - if (configValues.isEmpty() + if (resource.getConfigValueList().isEmpty() && resource.getVisibility().getLevel() == Level.PUBLIC) { - int sourceIndex = resource.getVisibility().getSource().getPathIdx(); - - String source = sourcePool.get(sourceIndex); - DataSource dataSource = DataSource.of(Paths.get(source)); - DataResourceXml dataResourceXml = DataResourceXml - .fromPublic(dataSource, resourceType, resource.getEntryId().getId()); - consumers.combiningConsumer.accept(fqn, dataResourceXml); - } else if (packageName.isEmpty()) {// This means this resource is not in the android sdk - Preconditions.checkArgument(configValues.size() == 1); - int sourceIndex = - configValues.get(0) - .getValue() - .getSource() - .getPathIdx(); + // Public resource definition. + int sourceIndex = resource.getVisibility().getSource().getPathIdx(); String source = sourcePool.get(sourceIndex); DataSource dataSource = DataSource.of(Paths.get(source)); - Value resourceValue = resource.getConfigValue(0).getValue(); DataResourceXml dataResourceXml = - DataResourceXml - .from(resourceValue, dataSource, resourceType, fullyQualifiedNames); - - if (resourceType == ResourceType.ID - || resourceType == ResourceType.STYLEABLE) { - consumers.combiningConsumer.accept(fqn, dataResourceXml); - } else { - consumers.overwritingConsumer.accept(fqn, dataResourceXml); + DataResourceXml.fromPublic(dataSource, resourceType, resource.getEntryId().getId()); + final FullyQualifiedName fqn = + createAndRecordFqn( + qualifiedReferenceInlineStatus, + packageName, + resourceType, + resource, + ImmutableList.of()); + consumers.combiningConsumer.accept(fqn, dataResourceXml); + } else if (!"android".equals(packageName)) { + // This means this resource is not in the android sdk, add it to the set. + for (ConfigValue configValue : resource.getConfigValueList()) { + FullyQualifiedName fqn = + createAndRecordFqn( + qualifiedReferenceInlineStatus, + packageName, + resourceType, + resource, + convertToQualifiers(configValue)); + + int sourceIndex = configValue.getValue().getSource().getPathIdx(); + + String source = sourcePool.get(sourceIndex); + DataSource dataSource = DataSource.of(Paths.get(source)); + + Value resourceValue = resource.getConfigValue(0).getValue(); + DataResourceXml dataResourceXml = + DataResourceXml.from( + resourceValue, dataSource, resourceType, qualifiedReferenceInlineStatus); + + if (!fqn.isOverwritable()) { + consumers.combiningConsumer.accept(fqn, dataResourceXml); + } else { + consumers.overwritingConsumer.accept(fqn, dataResourceXml); + } } + } else { + // In the sdk, just add the fqn for styleables + createAndRecordFqn( + qualifiedReferenceInlineStatus, + packageName, + resourceType, + resource, + ImmutableList.of()) + .toPrettyString(); } } } } } + private FullyQualifiedName createAndRecordFqn( + Map<String, Boolean> qualifiedReferenceInlineStatus, + String packageName, + ResourceType resourceType, + Resources.Entry resource, + List<String> of) { + Preconditions.checkArgument(!packageName.contains(":")); + final FullyQualifiedName fqn = + FullyQualifiedName.of( + packageName.isEmpty() ? FullyQualifiedName.DEFAULT_PACKAGE : packageName, + of, + resourceType, + resource.getName()); + // Record if the definition of the attr is defined in styleable. + // Currently, we consider any reference without a package as being defined inline. + qualifiedReferenceInlineStatus.put(fqn.asQualifiedReference(), packageName.isEmpty()); + return fqn; + } + + private List<String> convertToQualifiers(ConfigValue configValue) { + FolderConfiguration configuration = new FolderConfiguration(); + final Configuration protoConfig = configValue.getConfig(); + if (protoConfig.getMcc() > 0) { + configuration.setCountryCodeQualifier(new CountryCodeQualifier(protoConfig.getMcc())); + } + // special code for 0, as MNC can be zero + // https://android.googlesource.com/platform/frameworks/native/+/master/include/android/configuration.h#473 + if (protoConfig.getMnc() != 0) { + configuration.setNetworkCodeQualifier( + NetworkCodeQualifier.getQualifier( + String.format( + Locale.US, + "mnc%1$03d", + protoConfig.getMnc() == 0xffff ? 0 : protoConfig.getMnc()))); + } + + // locales are the wild, wild west: no enums. + if (!protoConfig.getLocale().isEmpty()) { + new LocaleQualifier().checkAndSet(protoConfig.getLocale(), configuration); + } + + if (LAYOUT_DIRECTION_MAP.containsKey(protoConfig.getLayoutDirection())) { + configuration.setLayoutDirectionQualifier( + new LayoutDirectionQualifier(LAYOUT_DIRECTION_MAP.get(protoConfig.getLayoutDirection()))); + } + + if (protoConfig.getSmallestScreenWidthDp() > 0) { + configuration.setSmallestScreenWidthQualifier( + new SmallestScreenWidthQualifier(protoConfig.getSmallestScreenWidthDp())); + } + + // screen dimension is defined if one number is greater than 0 + if (Math.max(protoConfig.getScreenHeight(), protoConfig.getScreenWidth()) > 0) { + configuration.setScreenDimensionQualifier( + new ScreenDimensionQualifier( + Math.max( + protoConfig.getScreenHeight(), + protoConfig.getScreenWidth()), // biggest is always first + Math.min(protoConfig.getScreenHeight(), protoConfig.getScreenWidth()))); + } + + if (protoConfig.getScreenWidthDp() > 0) { + configuration.setScreenWidthQualifier( + new ScreenWidthQualifier(protoConfig.getScreenWidthDp())); + } + + if (protoConfig.getScreenHeightDp() > 0) { + configuration.setScreenHeightQualifier( + new ScreenHeightQualifier(protoConfig.getScreenHeightDp())); + } + + if (LAYOUT_SIZE_MAP.containsKey(protoConfig.getScreenLayoutSize())) { + configuration.setScreenSizeQualifier( + new ScreenSizeQualifier(LAYOUT_SIZE_MAP.get(protoConfig.getScreenLayoutSize()))); + } + + if (SCREEN_LONG_MAP.containsKey(protoConfig.getScreenLayoutLong())) { + configuration.setScreenRatioQualifier( + new ScreenRatioQualifier(SCREEN_LONG_MAP.get(protoConfig.getScreenLayoutLong()))); + } + + if (SCREEN_ROUND_MAP.containsKey(protoConfig.getScreenRound())) { + configuration.setScreenRoundQualifier( + new ScreenRoundQualifier(SCREEN_ROUND_MAP.get(protoConfig.getScreenRound()))); + } + + if (SCREEN_ORIENTATION_MAP.containsKey(protoConfig.getOrientation())) { + configuration.setScreenOrientationQualifier( + new ScreenOrientationQualifier(SCREEN_ORIENTATION_MAP.get(protoConfig.getOrientation()))); + } + + if (SCREEN_UI_MODE.containsKey(protoConfig.getUiModeType())) { + configuration.setUiModeQualifier( + new UiModeQualifier(SCREEN_UI_MODE.get(protoConfig.getUiModeType()))); + } + + if (NIGHT_MODE_MAP.containsKey(protoConfig.getUiModeNight())) { + configuration.setNightModeQualifier( + new NightModeQualifier(NIGHT_MODE_MAP.get(protoConfig.getUiModeNight()))); + } + + if (DENSITY_MAP.containsKey(protoConfig.getDensity())) { + configuration.setDensityQualifier( + new DensityQualifier(DENSITY_MAP.get(protoConfig.getDensity()))); + } + + if (TOUCH_TYPE_MAP.containsKey(protoConfig.getTouchscreen())) { + configuration.setTouchTypeQualifier( + new TouchScreenQualifier(TOUCH_TYPE_MAP.get(protoConfig.getTouchscreen()))); + } + + if (KEYBOARD_STATE_MAP.containsKey(protoConfig.getKeysHidden())) { + configuration.setKeyboardStateQualifier( + new KeyboardStateQualifier(KEYBOARD_STATE_MAP.get(protoConfig.getKeysHidden()))); + } + + if (KEYBOARD_MAP.containsKey(protoConfig.getKeyboard())) { + configuration.setTextInputMethodQualifier( + new TextInputMethodQualifier(KEYBOARD_MAP.get(protoConfig.getKeyboard()))); + } + + if (NAV_STATE_MAP.containsKey(protoConfig.getNavHidden())) { + configuration.setNavigationStateQualifier( + new NavigationStateQualifier(NAV_STATE_MAP.get(protoConfig.getNavHidden()))); + } + + if (NAVIGATION_MAP.containsKey(protoConfig.getNavigation())) { + configuration.setNavigationMethodQualifier( + new NavigationMethodQualifier(NAVIGATION_MAP.get(protoConfig.getNavigation()))); + } + + if (protoConfig.getSdkVersion() > 0) { + configuration.setVersionQualifier(new VersionQualifier(protoConfig.getSdkVersion())); + } + + return Arrays.stream(configuration.getQualifiers()) + .map(ResourceQualifier::getFolderSegment) + .collect(toList()); + } + /** * Reads compiled resource data files and adds them to consumers * @@ -165,13 +476,14 @@ public class AndroidCompiledDataDeserializer implements AndroidDataDeserializer private void readCompiledFile( LittleEndianDataInputStream compiledFileStream, KeyValueConsumers consumers, - Factory fqnFactory) throws IOException { - //Skip aligned size. We don't need it here. + Factory fqnFactory) + throws IOException { + // Skip aligned size. We don't need it here. Preconditions.checkArgument(compiledFileStream.skipBytes(8) == 8); int resFileHeaderSize = compiledFileStream.readInt(); - //Skip data payload size. We don't need it here. + // Skip data payload size. We don't need it here. Preconditions.checkArgument(compiledFileStream.skipBytes(8) == 8); byte[] file = new byte[resFileHeaderSize]; @@ -201,9 +513,8 @@ public class AndroidCompiledDataDeserializer implements AndroidDataDeserializer } private void readAttributesFile( - InputStream resourceFileStream, - FileSystem fileSystem, - KeyValueConsumers consumers) throws IOException { + InputStream resourceFileStream, FileSystem fileSystem, KeyValueConsumers consumers) + throws IOException { Header header = Header.parseDelimitedFrom(resourceFileStream); List<DataKey> fullyQualifiedNames = new ArrayList<>(); @@ -219,10 +530,8 @@ public class AndroidCompiledDataDeserializer implements AndroidDataDeserializer SerializeFormat.DataValue protoValue = SerializeFormat.DataValue.parseDelimitedFrom(resourceFileStream); DataSource source = sourceTable.sourceFromId(protoValue.getSourceId()); - DataResourceXml dataResourceXml = - (DataResourceXml) DataResourceXml.from(protoValue, source); - AttributeType attributeType = - AttributeType.valueOf(protoValue.getXmlValue().getValueType()); + DataResourceXml dataResourceXml = (DataResourceXml) DataResourceXml.from(protoValue, source); + AttributeType attributeType = AttributeType.valueOf(protoValue.getXmlValue().getValueType()); if (attributeType.isCombining()) { consumers.combiningConsumer.accept(fullyQualifiedName, dataResourceXml); @@ -276,18 +585,20 @@ public class AndroidCompiledDataDeserializer implements AndroidDataDeserializer int resourceType = dataInputStream.readInt(); if (resourceType == 0) { // 0 is a resource table - readResourceTable(dataInputStream, consumers, fqnFactory); + readResourceTable(dataInputStream, consumers); } else if (resourceType == 1) { // 1 is a resource file readCompiledFile(dataInputStream, consumers, fqnFactory); } else { - throw new DeserializationException("aapt2 version mismatch.", - new DeserializationException(String.format( - "Unexpected tag for resourceType %s expected 0 or 1 in %s." - + "\n Last known good values:" - + "\n\tmagicNumber 1414545729 (is %s)" - + "\n\tformatVersion 1 (is %s)" - + "\n\tnumberOfEntries 1 (is %s)", - resourceType, fileZipPath, magicNumber, formatVersion, numberOfEntries))); + throw new DeserializationException( + "aapt2 version mismatch.", + new DeserializationException( + String.format( + "Unexpected tag for resourceType %s expected 0 or 1 in %s." + + "\n Last known good values:" + + "\n\tmagicNumber 1414545729 (is %s)" + + "\n\tformatVersion 1 (is %s)" + + "\n\tnumberOfEntries 1 (is %s)", + resourceType, fileZipPath, magicNumber, formatVersion, numberOfEntries))); } } } diff --git a/src/tools/android/java/com/google/devtools/build/android/DataResourceXml.java b/src/tools/android/java/com/google/devtools/build/android/DataResourceXml.java index 2b39c449e6..59a6fa3504 100644 --- a/src/tools/android/java/com/google/devtools/build/android/DataResourceXml.java +++ b/src/tools/android/java/com/google/devtools/build/android/DataResourceXml.java @@ -46,7 +46,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Iterator; import java.util.Map; -import java.util.Map.Entry; import java.util.Objects; import javax.xml.stream.FactoryConfigurationError; import javax.xml.stream.XMLEventReader; @@ -174,7 +173,7 @@ public class DataResourceXml implements DataResource { Value protoValue, DataSource source, ResourceType resourceType, - Map<String, Entry<FullyQualifiedName, Boolean>> fullyQualifiedNames) + Map<String, Boolean> fullyQualifiedNames) throws InvalidProtocolBufferException { DataResourceXml dataResourceXml = createWithNamespaces( @@ -212,9 +211,7 @@ public class DataResourceXml implements DataResource { } private static XmlResourceValue valueFromProto( - Value proto, - ResourceType resourceType, - Map<String, Entry<FullyQualifiedName, Boolean>> fullyQualifiedNames) + Value proto, ResourceType resourceType, Map<String, Boolean> qualifiedReferenceToInlineStatus) throws InvalidProtocolBufferException { switch (resourceType) { case STYLE: @@ -226,7 +223,7 @@ public class DataResourceXml implements DataResource { case ATTR: return AttrXmlResourceValue.from(proto); case STYLEABLE: - return StyleableXmlResourceValue.from(proto, fullyQualifiedNames); + return StyleableXmlResourceValue.from(proto, qualifiedReferenceToInlineStatus); case ID: return IdXmlResourceValue.of(); case DIMEN: diff --git a/src/tools/android/java/com/google/devtools/build/android/FullyQualifiedName.java b/src/tools/android/java/com/google/devtools/build/android/FullyQualifiedName.java index 9344412390..3663c34673 100644 --- a/src/tools/android/java/com/google/devtools/build/android/FullyQualifiedName.java +++ b/src/tools/android/java/com/google/devtools/build/android/FullyQualifiedName.java @@ -20,6 +20,7 @@ import com.android.ide.common.resources.configuration.ResourceQualifier; import com.android.resources.ResourceType; import com.google.common.base.Joiner; import com.google.common.base.MoreObjects; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.Iterators; @@ -33,6 +34,7 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; @@ -130,6 +132,23 @@ public class FullyQualifiedName implements DataKey { protoKey.getKeyValue()); } + static final Pattern QUALIFIED_REFERENCE = + Pattern.compile("((?<package>[^:]+):)?(?<type>\\w+)/(?<name>\\w+)"); + + public static FullyQualifiedName fromReference(String qualifiedReference) { + final Matcher matcher = QUALIFIED_REFERENCE.matcher(qualifiedReference); + Preconditions.checkArgument( + matcher.find(), + "%s is not a reference. Expected %s", + qualifiedReference, + QUALIFIED_REFERENCE.pattern()); + return of( + Optional.ofNullable(matcher.group("package")).orElse(DEFAULT_PACKAGE), + ImmutableList.of(), + ResourceType.getEnum(matcher.group("type")), + matcher.group("name")); + } + public static void logCacheUsage(Logger logger) { logger.fine( String.format( @@ -186,6 +205,16 @@ public class FullyQualifiedName implements DataKey { return name; } + /** Provides the name qualified by the package it belongs to. */ + public String qualifiedName() { + return (pkg.equals(DEFAULT_PACKAGE) ? "" : pkg + ":") + name; + } + + public String asQualifiedReference() { + return String.format( + "%s%s/%s", (pkg.equals(DEFAULT_PACKAGE) ? "" : pkg + ":"), type.getName(), name); + } + public ResourceType type() { if (type instanceof ResourceTypeWrapper) { return ((ResourceTypeWrapper) type).resourceType; diff --git a/src/tools/android/java/com/google/devtools/build/android/PlaceholderIdFieldInitializerBuilder.java b/src/tools/android/java/com/google/devtools/build/android/PlaceholderIdFieldInitializerBuilder.java index 12b7abe4ae..ab1dee800d 100644 --- a/src/tools/android/java/com/google/devtools/build/android/PlaceholderIdFieldInitializerBuilder.java +++ b/src/tools/android/java/com/google/devtools/build/android/PlaceholderIdFieldInitializerBuilder.java @@ -165,6 +165,7 @@ class PlaceholderIdFieldInitializerBuilder { private static String normalizeAttrName(String attrName) { // In addition to ".", attributes can have ":", e.g., for "android:textColor". + Preconditions.checkArgument(!attrName.contains("::"), "invalid name %s", attrName); return normalizeName(attrName).replace(':', '_'); } @@ -240,7 +241,7 @@ class PlaceholderIdFieldInitializerBuilder { styleableAttrs.put(normalizedStyleableName, normalizedAttrs); } for (Map.Entry<FullyQualifiedName, Boolean> attrEntry : attrs.entrySet()) { - String normalizedAttrName = normalizeAttrName(attrEntry.getKey().name()); + String normalizedAttrName = normalizeAttrName(attrEntry.getKey().qualifiedName()); normalizedAttrs.put(normalizedAttrName, attrEntry.getValue()); } } diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/StyleableXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/StyleableXmlResourceValue.java index c9661690cf..72707cf87d 100644 --- a/src/tools/android/java/com/google/devtools/build/android/xml/StyleableXmlResourceValue.java +++ b/src/tools/android/java/com/google/devtools/build/android/xml/StyleableXmlResourceValue.java @@ -18,6 +18,7 @@ import com.android.aapt.Resources.Value; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.MoreObjects; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; import com.google.common.collect.Iterables; @@ -165,16 +166,22 @@ public class StyleableXmlResourceValue implements XmlResourceValue { } public static XmlResourceValue from( - Value proto, Map<String, Entry<FullyQualifiedName, Boolean>> fullyQualifiedNames) { + Value proto, Map<String, Boolean> qualifiedReferenceInlineStatus) { Map<FullyQualifiedName, Boolean> attributes = new HashMap<>(); Styleable styleable = proto.getCompoundValue().getStyleable(); for (Styleable.Entry entry : styleable.getEntryList()) { - String attrName = entry.getAttr().getName(); - - Entry<FullyQualifiedName, Boolean> fqnEntry = fullyQualifiedNames.get(attrName); - attributes.put(fqnEntry.getKey(), fqnEntry.getValue()); - fqnEntry.setValue(false); + final FullyQualifiedName reference = + FullyQualifiedName.fromReference(entry.getAttr().getName()); + final String qualifiedReference = reference.asQualifiedReference(); + Preconditions.checkArgument( + qualifiedReferenceInlineStatus.containsKey(qualifiedReference), + "Styleable reference %s is not in %s", + qualifiedReference, + qualifiedReferenceInlineStatus.keySet()); + + attributes.put(reference, qualifiedReferenceInlineStatus.get(qualifiedReference)); + qualifiedReferenceInlineStatus.put(qualifiedReference, false); } return of(ImmutableMap.copyOf(attributes)); @@ -202,12 +209,12 @@ public class StyleableXmlResourceValue implements XmlResourceValue { /** * Combines this instance with another {@link StyleableXmlResourceValue}. * - * Defining two Styleables (undocumented in the official Android Docs) with the same - * {@link FullyQualifiedName} results in a single Styleable containing a union of all the - * attribute references. + * <p>Defining two Styleables (undocumented in the official Android Docs) with the same {@link + * FullyQualifiedName} results in a single Styleable containing a union of all the attribute + * references. * - * @param value Another {@link StyleableXmlResourceValue} with the same - * {@link FullyQualifiedName}. + * @param value Another {@link StyleableXmlResourceValue} with the same {@link + * FullyQualifiedName}. * @return {@link StyleableXmlResourceValue} containing a sorted union of the attribute * references. * @throws IllegalArgumentException if value is not an {@link StyleableXmlResourceValue}. |