| /* | |
| * Copyright (C) 2015 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. | |
| */ | |
| #include "link/Linkers.h" | |
| #include <algorithm> | |
| #include "android-base/logging.h" | |
| #include "ResourceTable.h" | |
| #include "SdkConstants.h" | |
| #include "ValueVisitor.h" | |
| #include "trace/TraceBuffer.h" | |
| using android::ConfigDescription; | |
| namespace aapt { | |
| bool ShouldGenerateVersionedResource(const ResourceEntry* entry, | |
| const ConfigDescription& config, | |
| const ApiVersion sdk_version_to_generate) { | |
| // We assume the caller is trying to generate a version greater than the current configuration. | |
| CHECK(sdk_version_to_generate > config.sdkVersion); | |
| return sdk_version_to_generate < FindNextApiVersionForConfig(entry, config); | |
| } | |
| ApiVersion FindNextApiVersionForConfig(const ResourceEntry* entry, | |
| const ConfigDescription& config) { | |
| const auto end_iter = entry->values.end(); | |
| auto iter = entry->values.begin(); | |
| for (; iter != end_iter; ++iter) { | |
| if ((*iter)->config == config) { | |
| break; | |
| } | |
| } | |
| // The source config came from this list, so it should be here. | |
| CHECK(iter != entry->values.end()); | |
| ++iter; | |
| // The next configuration either only varies in sdkVersion, or it is completely different | |
| // and therefore incompatible. If it is incompatible, we must generate the versioned resource. | |
| // NOTE: The ordering of configurations takes sdkVersion as higher precedence than other | |
| // qualifiers, so we need to iterate through the entire list to be sure there | |
| // are no higher sdk level versions of this resource. | |
| ConfigDescription temp_config(config); | |
| for (; iter != end_iter; ++iter) { | |
| temp_config.sdkVersion = (*iter)->config.sdkVersion; | |
| if (temp_config == (*iter)->config) { | |
| // The two configs are the same, return the sdkVersion. | |
| return (*iter)->config.sdkVersion; | |
| } | |
| } | |
| // Didn't find another config with a different sdk version, so return the highest possible value. | |
| return std::numeric_limits<ApiVersion>::max(); | |
| } | |
| bool AutoVersioner::Consume(IAaptContext* context, ResourceTable* table) { | |
| TRACE_NAME("AutoVersioner::Consume"); | |
| CloningValueTransformer cloner(&table->string_pool); | |
| for (auto& package : table->packages) { | |
| for (auto& type : package->types) { | |
| if (type->type != ResourceType::kStyle) { | |
| continue; | |
| } | |
| for (auto& entry : type->entries) { | |
| for (size_t i = 0; i < entry->values.size(); i++) { | |
| ResourceConfigValue* config_value = entry->values[i].get(); | |
| if (config_value->config.sdkVersion >= SDK_LOLLIPOP_MR1) { | |
| // If this configuration is only used on L-MR1 then we don't need | |
| // to do anything since we use private attributes since that | |
| // version. | |
| continue; | |
| } | |
| if (Style* style = ValueCast<Style>(config_value->value.get())) { | |
| Maybe<ApiVersion> min_sdk_stripped; | |
| std::vector<Style::Entry> stripped; | |
| auto iter = style->entries.begin(); | |
| while (iter != style->entries.end()) { | |
| CHECK(bool(iter->key.id)) << "IDs must be assigned and linked"; | |
| // Find the SDK level that is higher than the configuration | |
| // allows. | |
| const ApiVersion sdk_level = FindAttributeSdkLevel(iter->key.id.value()); | |
| if (sdk_level > std::max<ApiVersion>(config_value->config.sdkVersion, 1)) { | |
| // Record that we are about to strip this. | |
| stripped.emplace_back(std::move(*iter)); | |
| // We use the smallest SDK level to generate the new style. | |
| if (min_sdk_stripped) { | |
| min_sdk_stripped = std::min(min_sdk_stripped.value(), sdk_level); | |
| } else { | |
| min_sdk_stripped = sdk_level; | |
| } | |
| // Erase this from this style. | |
| iter = style->entries.erase(iter); | |
| continue; | |
| } | |
| ++iter; | |
| } | |
| if (min_sdk_stripped && !stripped.empty()) { | |
| // We found attributes from a higher SDK level. Check that | |
| // there is no other defined resource for the version we want to | |
| // generate. | |
| if (ShouldGenerateVersionedResource(entry.get(), | |
| config_value->config, | |
| min_sdk_stripped.value())) { | |
| // Let's create a new Style for this versioned resource. | |
| ConfigDescription new_config(config_value->config); | |
| new_config.sdkVersion = static_cast<uint16_t>(min_sdk_stripped.value()); | |
| std::unique_ptr<Style> new_style(style->Transform(cloner)); | |
| new_style->SetComment(style->GetComment()); | |
| new_style->SetSource(style->GetSource()); | |
| // Move the previously stripped attributes into this style. | |
| new_style->entries.insert( | |
| new_style->entries.end(), | |
| std::make_move_iterator(stripped.begin()), | |
| std::make_move_iterator(stripped.end())); | |
| // Insert the new Resource into the correct place. | |
| entry->FindOrCreateValue(new_config, {})->value = std::move(new_style); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| } // namespace aapt |