| package com.android.tools.idea.editors.theme; |
| |
| import com.android.ide.common.rendering.api.ResourceValue; |
| import com.android.ide.common.rendering.api.StyleResourceValue; |
| import com.android.ide.common.resources.ResourceRepository; |
| import com.android.ide.common.resources.ResourceResolver; |
| import com.android.resources.ResourceType; |
| import com.android.tools.idea.configurations.Configuration; |
| import com.android.tools.idea.editors.theme.datamodels.ThemeEditorStyle; |
| import com.android.tools.idea.rendering.AppResourceRepository; |
| import com.android.tools.idea.rendering.LocalResourceRepository; |
| import com.android.tools.idea.rendering.ModuleResourceRepository; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.util.Pair; |
| import org.jetbrains.android.facet.AndroidFacet; |
| import org.jetbrains.android.util.AndroidUtils; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| |
| import static com.android.ide.common.resources.ResourceResolver.THEME_NAME; |
| import static com.android.ide.common.resources.ResourceResolver.THEME_NAME_DOT; |
| |
| /** |
| * Class that provides methods to resolve themes for a given configuration. |
| */ |
| public class ThemeResolver { |
| private final Map<String, ThemeEditorStyle> myThemeByName = Maps.newHashMap(); |
| private final ImmutableList<ThemeEditorStyle> myFrameworkThemes; |
| private final ImmutableList<ThemeEditorStyle> myLocalThemes; |
| private final ImmutableList<ThemeEditorStyle> myExternalLibraryThemes; |
| |
| private final Configuration myConfiguration; |
| private final ResourceResolver myResolver; |
| |
| public ThemeResolver(@NotNull Configuration configuration) { |
| myConfiguration = configuration; |
| myResolver = configuration.getResourceResolver(); |
| |
| if (myResolver == null) { |
| throw new IllegalArgumentException("Acquired ResourceResolver is null, not an Android module?"); |
| } |
| |
| myFrameworkThemes = fillThemeResolverFromStyles(resolveFrameworkThemes()); |
| |
| final ImmutableList.Builder<ThemeEditorStyle> localThemes = ImmutableList.builder(); |
| for (Pair<StyleResourceValue, Module> pair : resolveLocallyDefinedModuleThemes()) { |
| final ThemeEditorStyle theme = constructThemeFromResourceValue(pair.getFirst(), pair.getSecond()); |
| if (theme != null) { |
| localThemes.add(theme); |
| } |
| } |
| myLocalThemes = localThemes.build(); |
| |
| // resolveNonFrameworkThemes() returns all themes available from the current module, including library themes. |
| // Because all local themes would be added at previous step to myLocalThemes, they'll be ignored |
| // at this step, and all that we've got here is library themes. |
| myExternalLibraryThemes = fillThemeResolverFromStyles(resolveNonFrameworkThemes()); |
| } |
| |
| /** |
| * Create a ThemeEditorStyle instance stored in ThemeResolver, which can be added to one of theme lists. |
| * @return null if theme with this name was already added or resolution has failed |
| */ |
| @Nullable |
| private ThemeEditorStyle constructThemeFromResourceValue(@NotNull StyleResourceValue value, @Nullable Module sourceModule) { |
| final String name = ResolutionUtils.getQualifiedStyleName(value); |
| |
| if (myThemeByName.containsKey(name)) { |
| return null; |
| } |
| |
| final ThemeEditorStyle theme = ResolutionUtils.getStyle(myConfiguration, name, sourceModule); |
| if (theme != null) { |
| myThemeByName.put(name, theme); |
| } |
| |
| return theme; |
| } |
| |
| private ImmutableList<ThemeEditorStyle> fillThemeResolverFromStyles(@NotNull List<StyleResourceValue> source) { |
| ImmutableList.Builder<ThemeEditorStyle> builder = ImmutableList.builder(); |
| |
| for (StyleResourceValue value : source) { |
| ThemeEditorStyle theme = constructThemeFromResourceValue(value, null); |
| if (theme != null) { |
| builder.add(theme); |
| } |
| } |
| |
| return builder.build(); |
| } |
| |
| @NotNull |
| private List<StyleResourceValue> resolveFrameworkThemes() { |
| ResourceRepository repository = myConfiguration.getFrameworkResources(); |
| if (repository == null) { |
| return Collections.emptyList(); |
| } |
| |
| return getThemes(repository.getConfiguredResources(myConfiguration.getFullConfig()).get(ResourceType.STYLE), true /*isFramework*/); |
| } |
| |
| /** |
| * Resolve all non-framework themes available from module of passed Configuration |
| */ |
| @NotNull |
| private List<StyleResourceValue> resolveNonFrameworkThemes() { |
| LocalResourceRepository repository = AppResourceRepository.getAppResources(myConfiguration.getModule(), true); |
| if (repository == null) { |
| return Collections.emptyList(); |
| } |
| |
| return getThemes(repository.getConfiguredResources(ResourceType.STYLE, myConfiguration.getFullConfig()), false /*isFramework*/); |
| } |
| |
| /** |
| * Resolve all themes available from passed Configuration's source module and its dependencies which are defined |
| * in the current project (doesn't include themes available from libraries) |
| */ |
| @NotNull |
| private List<Pair<StyleResourceValue, Module>> resolveLocallyDefinedModuleThemes() { |
| final Module module = myConfiguration.getModule(); |
| final List<Pair<StyleResourceValue, Module>> result = Lists.newArrayList(); |
| |
| fillModuleResources(module, ModuleResourceRepository.getModuleResources(module, true), result); |
| |
| final List<AndroidFacet> allAndroidDependencies = AndroidUtils.getAllAndroidDependencies(module, false); |
| for (AndroidFacet facet : allAndroidDependencies) { |
| fillModuleResources(facet.getModule(), facet.getModuleResources(true), result); |
| } |
| |
| return result; |
| } |
| |
| private void fillModuleResources(@NotNull Module module, |
| @Nullable LocalResourceRepository repository, |
| @NotNull List<Pair<StyleResourceValue, Module>> sink) { |
| if (repository == null) { |
| return; |
| } |
| |
| for (StyleResourceValue value : getThemes(repository.getConfiguredResources(ResourceType.STYLE, myConfiguration.getFullConfig()), false)) { |
| sink.add(Pair.create(value, module)); |
| } |
| } |
| |
| @NotNull |
| private List<StyleResourceValue> getThemes(@NotNull Map<String, ResourceValue> styles, |
| boolean isFramework) { |
| // Collect the themes out of all the styles. |
| Collection<ResourceValue> values = styles.values(); |
| List<StyleResourceValue> themes = new ArrayList<StyleResourceValue>(values.size()); |
| |
| if (!isFramework) { |
| Map<ResourceValue, Boolean> cache = Maps.newHashMapWithExpectedSize(values.size()); |
| for (ResourceValue value : values) { |
| if (value instanceof StyleResourceValue) { |
| StyleResourceValue styleValue = (StyleResourceValue)value; |
| if (myResolver.isTheme(styleValue, cache)) { |
| themes.add(styleValue); |
| } |
| } |
| } |
| return themes; |
| } |
| |
| // For the framework themes the computation is easier |
| for (ResourceValue value : values) { |
| String name = value.getName(); |
| if (name.startsWith(THEME_NAME_DOT) || name.equals(THEME_NAME)) { |
| themes.add((StyleResourceValue)value); |
| } |
| } |
| return themes; |
| } |
| |
| @Nullable |
| public ThemeEditorStyle getTheme(@NotNull String themeName) { |
| return myThemeByName.get(themeName); |
| } |
| |
| /** |
| * Returns the list of themes available from the module passed Configuration comes from and all its dependencies. |
| */ |
| @NotNull |
| public ImmutableList<ThemeEditorStyle> getLocalThemes() { |
| return myLocalThemes; |
| } |
| |
| /** |
| * Returns the list of themes that come from external libraries (e.g. AppCompat) |
| */ |
| @NotNull |
| public ImmutableList<ThemeEditorStyle> getExternalLibraryThemes() { |
| return myExternalLibraryThemes; |
| } |
| |
| /** |
| * Returns the list of available framework themes. |
| */ |
| @NotNull |
| public ImmutableList<ThemeEditorStyle> getFrameworkThemes() { |
| return myFrameworkThemes; |
| } |
| |
| public int getThemesCount() { |
| return myFrameworkThemes.size() + myExternalLibraryThemes.size() + myLocalThemes.size(); |
| } |
| } |