/* * Copyright (C) 2018 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 "optimize/ResourcePathShortener.h" #include #include #include "androidfw/StringPiece.h" #include "ResourceTable.h" #include "ValueVisitor.h" #include "util/Util.h" static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789-_"; namespace aapt { ResourcePathShortener::ResourcePathShortener( std::map& path_map_out) : path_map_(path_map_out) { } std::string ShortenFileName(const android::StringPiece& file_path, int output_length) { std::size_t hash_num = std::hash{}(file_path); std::string result = ""; // Convert to (modified) base64 so that it is a proper file path. for (int i = 0; i < output_length; i++) { uint8_t sextet = hash_num & 0x3f; hash_num >>= 6; result += base64_chars[sextet]; } return result; } // Return the optimal hash length such that at most 10% of resources collide in // their shortened path. // Reference: http://matt.might.net/articles/counting-hash-collisions/ int OptimalShortenedLength(int num_resources) { if (num_resources > 4000) { return 3; } else { return 2; } } std::string GetShortenedPath(const android::StringPiece& shortened_filename, const android::StringPiece& extension, int collision_count) { std::string shortened_path = "res/" + shortened_filename.to_string(); if (collision_count > 0) { shortened_path += std::to_string(collision_count); } shortened_path += extension; return shortened_path; } // implement custom comparator of FileReference pointers so as to use the // underlying filepath as key rather than the integer address. This is to ensure // determinism of output for colliding files. struct PathComparator { bool operator() (const FileReference* lhs, const FileReference* rhs) const { return lhs->path->compare(*rhs->path); } }; bool ResourcePathShortener::Consume(IAaptContext* context, ResourceTable* table) { // used to detect collisions std::unordered_set shortened_paths; std::set file_refs; for (auto& package : table->packages) { for (auto& type : package->types) { for (auto& entry : type->entries) { for (auto& config_value : entry->values) { FileReference* file_ref = ValueCast(config_value->value.get()); if (file_ref) { file_refs.insert(file_ref); } } } } } int num_chars = OptimalShortenedLength(file_refs.size()); for (auto& file_ref : file_refs) { android::StringPiece res_subdir, actual_filename, extension; util::ExtractResFilePathParts(*file_ref->path, &res_subdir, &actual_filename, &extension); // Android detects ColorStateLists via pathname, skip res/color* if (util::StartsWith(res_subdir, "res/color")) continue; std::string shortened_filename = ShortenFileName(*file_ref->path, num_chars); int collision_count = 0; std::string shortened_path = GetShortenedPath(shortened_filename, extension, collision_count); while (shortened_paths.find(shortened_path) != shortened_paths.end()) { collision_count++; shortened_path = GetShortenedPath(shortened_filename, extension, collision_count); } shortened_paths.insert(shortened_path); path_map_.insert({*file_ref->path, shortened_path}); file_ref->path = table->string_pool.MakeRef(shortened_path, file_ref->path.GetContext()); } return true; } } // namespace aapt