blob: 054e18cdb198384653582bdb1c417cbd47be7816 [file] [log] [blame]
/*
* Copyright 2012-present Facebook, Inc.
*
* 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.facebook.buck.util;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Table;
import java.io.File;
import java.util.Map;
import java.util.Set;
public class Filters {
public static final ImmutableMap<String, ImmutableList<String>> ORDERING = ImmutableMap.of(
"mdpi", ImmutableList.of("mdpi", "", "hdpi", "xhdpi"),
"hdpi", ImmutableList.of("hdpi", "xhdpi", "mdpi", ""),
"xhdpi", ImmutableList.of("xhdpi", "hdpi", "mdpi", "")
);
public static class Qualifiers {
public final String density;
public final String others;
/**
* Splits off the density qualifier from a drawable.
* @param file path to a drawable
*/
public Qualifiers(File file) {
StringBuilder densityBuilder = new StringBuilder();
StringBuilder othersBuilder = new StringBuilder();
for (String qualifier : file.getParentFile().getName().split("-")) {
if (qualifier.equals("drawable")) { // We're assuming they're all drawables.
continue;
}
StringBuilder whichBuilder =
ORDERING.containsKey(qualifier) ? densityBuilder : othersBuilder;
whichBuilder.append((MoreStrings.isEmpty(whichBuilder) ? "" : "-") + qualifier);
}
this.density = densityBuilder.toString();
this.others = othersBuilder.toString();
}
}
/**
* Takes a list of image files (as paths), and a target density (mdpi, hdpi, xhdpi), and
* returns a list of files which can be safely left out when building an APK for phones with that
* screen density. That APK will run on other screens as well but look worse due to scaling.
* <p>
* Each combination of non-density qualifiers is processed separately. For example, if we have
* {@code drawable-hdpi, drawable-mdpi, drawable-xhdpi, drawable-hdpi-ro}, for a target of {@code
* mdpi}, we'll be keeping {@code drawable-mdpi, drawable-hdpi-ro}.
* @param candidates list of paths to image files
* @param targetDensity density to keep
* @return set of files to remove
*/
@VisibleForTesting
static Set<File> onlyOneImage(Iterable<String> candidates, String targetDensity) {
ImmutableSet.Builder<File> removals = ImmutableSet.builder();
Table<String, String, String> imageValues = HashBasedTable.create();
// Create mappings for drawables. If candidate == "<base>/drawable-<dpi>-<other>/<filename>",
// then we'll record a mapping of the form ("<base>/<filename>/<other>", "<dpi>") -> candidate.
for (String candidate : candidates) {
File f = new File(candidate);
Qualifiers qualifiers = new Qualifiers(f);
String filename = f.getName();
String density = qualifiers.density;
String resDirectory = f.getParentFile().getParent();
String key = String.format("%s/%s/%s", resDirectory, filename, qualifiers.others);
imageValues.put(key, density, candidate);
}
for (String key : imageValues.rowKeySet()) {
Map<String, String> options = imageValues.row(key);
Set<String> densitiesForKey = options.keySet();
ImmutableList<String> resOrder = ORDERING.get(targetDensity);
// If there's only one density, or there are densities we don't work with, skip this image.
if (options.size() == 1 || !resOrder.containsAll(densitiesForKey)) {
continue;
}
// Go through DPIs in order of preference and keep the first one actually present.
String keep = resOrder.get(0);
for (String res : resOrder) {
if (densitiesForKey.contains(res)) {
keep = res;
break;
}
}
// Mark all the others for removal.
for (String density : densitiesForKey) {
if (keep.equals(density)) {
continue;
}
removals.add(new File(options.get(density)).getAbsoluteFile());
}
}
return removals.build();
}
/**
* Given a list of paths of available drawables, and a target screen density, returns a
* {@link com.google.common.base.Predicate} that fails for drawables of a different
* density, whenever they can be safely removed.
* @param candidates list of available drawables
* @param targetDensity one of {@code "xhdpi"}, {@code "hdpi"} or {@code "mdpi"}
* @return a predicate as above
*/
public static Predicate<File> createImageDensityFilter(Iterable<String> candidates,
String targetDensity) {
Preconditions.checkArgument(ORDERING.containsKey(targetDensity));
final Set<File> removals = onlyOneImage(candidates, targetDensity);
return new Predicate<File>() {
@Override
public boolean apply(File pathname) {
return !removals.contains(pathname.getAbsoluteFile());
}
};
}
}