| /* |
| * Copyright (C) 2022 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.build.shrinker; |
| |
| import com.android.aapt.Resources.ResourceTable; |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| import com.android.build.shrinker.obfuscation.ObfuscatedClasses; |
| import com.android.ide.common.resources.usage.ResourceStore; |
| import com.android.ide.common.resources.usage.ResourceUsageModel.Resource; |
| import com.android.resources.ResourceType; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Sets; |
| import java.io.IOException; |
| import java.io.UncheckedIOException; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| |
| /** |
| * Represents resource shrinker state that is shared between shrinker stages ResourcesGatherer, |
| * ObfuscationMappingsRecorder, ResourceUsageRecorder, ResourcesGraphBuilder and each stage |
| * contributes an information via changing its state. |
| */ |
| public class ResourceShrinkerModel { |
| |
| private final ShrinkerDebugReporter debugReporter; |
| |
| private final ResourceStore resourceStore; |
| private ObfuscatedClasses obfuscatedClasses = ObfuscatedClasses.NO_OBFUSCATION; |
| |
| private final Set<String> strings = Sets.newHashSetWithExpectedSize(300); |
| |
| private final Map<String, ResourceTable> resourceTableCache = Maps.newHashMap(); |
| |
| private boolean foundGetIdentifier = false; |
| private boolean foundWebContent = false; |
| |
| public ResourceShrinkerModel( |
| ShrinkerDebugReporter debugReporter, boolean supportMultipackages) { |
| this.debugReporter = debugReporter; |
| resourceStore = new ResourceStore(supportMultipackages); |
| } |
| |
| @NonNull |
| public ShrinkerDebugReporter getDebugReporter() { |
| return debugReporter; |
| } |
| |
| @NonNull |
| public ResourceStore getResourceStore() { |
| return resourceStore; |
| } |
| |
| @NonNull |
| public ObfuscatedClasses getObfuscatedClasses() { |
| return obfuscatedClasses; |
| } |
| |
| /** Reports recorded mappings from obfuscated classes to original classes */ |
| public void setObfuscatedClasses(@NonNull ObfuscatedClasses obfuscatedClasses) { |
| this.obfuscatedClasses = obfuscatedClasses; |
| } |
| |
| /** Adds a new gathered resource to model. */ |
| @NonNull |
| public Resource addResource( |
| @NonNull ResourceType type, |
| @Nullable String packageName, |
| @NonNull String name, |
| @Nullable String value) { |
| int intValue = -1; |
| try { |
| intValue = value != null ? Integer.decode(value) : -1; |
| } catch (NumberFormatException e) { |
| // ignore |
| } |
| return addResource(type, packageName, name, intValue); |
| } |
| |
| /** Adds a new gathered resource to model. */ |
| @NonNull |
| public Resource addResource( |
| @NonNull ResourceType type, |
| @Nullable String packageName, |
| @NonNull String name, |
| int value) { |
| return resourceStore.addResource(new Resource(packageName, type, name, value)); |
| } |
| |
| /** Adds string constant found in code to strings pool. */ |
| public void addStringConstant(@NonNull String string) { |
| strings.add(string); |
| } |
| |
| /** Returns is reference to {@code Resources#getIdentifier} is found in code. */ |
| public boolean isFoundGetIdentifier() { |
| return foundGetIdentifier; |
| } |
| |
| /** Reports that reference to {@code Resources#getIdentifier} is found in code. */ |
| public void setFoundGetIdentifier(boolean foundGetIdentifier) { |
| this.foundGetIdentifier = foundGetIdentifier; |
| } |
| |
| /** Returns is web content is found in code. */ |
| public boolean isFoundWebContent() { |
| return foundWebContent; |
| } |
| |
| /** Reports that reference to web content is found in code. */ |
| public void setFoundWebContent(boolean foundWebContent) { |
| this.foundWebContent = foundWebContent; |
| } |
| |
| /** Returns all string constants gathered from compiled classes. */ |
| public Set<String> getStrings() { |
| return strings; |
| } |
| |
| /** |
| * Mark resources that match string constants as reachable in case invocation of |
| * {@code Resources#getIdentifier} or web content is found in code and safe mode in enabled. |
| */ |
| public void keepPossiblyReferencedResources() { |
| if (strings.isEmpty() |
| || !resourceStore.getSafeMode() |
| || (!foundGetIdentifier && !foundWebContent)) { |
| // No calls to android.content.res.Resources#getIdentifier; or user specifically asked |
| // for us not to guess resources to keep |
| return; |
| } |
| |
| debugReporter.debug(() -> "" |
| + "android.content.res.Resources#getIdentifier present: " + foundGetIdentifier + "\n" |
| + "Web content present: " + foundWebContent + "\n" |
| + "Referenced Strings:\n" |
| + strings.stream() |
| .map(s -> s.trim().replace("\n", "\\n")) |
| .filter(s -> !s.isEmpty()) |
| .map(s -> s.length() > 40 ? s.substring(0, 37) + "..." : s) |
| .collect(Collectors.joining("\n")) |
| ); |
| |
| new PossibleResourcesMarker(debugReporter, resourceStore, strings, foundWebContent) |
| .markPossibleResourcesReachable(); |
| } |
| |
| /** |
| * Reads resource table from specified path and stores it in cache to be able to reuse it if |
| * the same resource table is requested by another unit. |
| */ |
| public ResourceTable readResourceTable(Path resourceTablePath) { |
| return resourceTableCache.computeIfAbsent(resourceTablePath.toString(), (path) -> { |
| try { |
| return ResourceTable.parseFrom(Files.readAllBytes(resourceTablePath)); |
| } catch (IOException e) { |
| throw new UncheckedIOException(e); |
| } |
| }); |
| } |
| } |