blob: 9009b71205a1ff51a6e69680619961ee088d4378 [file] [log] [blame]
Mohamed Heikalc7694032018-11-07 16:49:02 -05001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "optimize/ResourcePathShortener.h"
18
Mohamed Heikalc7694032018-11-07 16:49:02 -050019#include <unordered_set>
20
21#include "androidfw/StringPiece.h"
22
23#include "ResourceTable.h"
24#include "ValueVisitor.h"
25
26
27static const std::string base64_chars =
28 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
29 "abcdefghijklmnopqrstuvwxyz"
30 "0123456789-_";
31
32namespace aapt {
33
34ResourcePathShortener::ResourcePathShortener(
35 std::map<std::string, std::string>& path_map_out)
36 : path_map_(path_map_out) {
37}
38
39std::string ShortenFileName(const android::StringPiece& file_path, int output_length) {
40 std::size_t hash_num = std::hash<android::StringPiece>{}(file_path);
41 std::string result = "";
42 // Convert to (modified) base64 so that it is a proper file path.
43 for (int i = 0; i < output_length; i++) {
44 uint8_t sextet = hash_num & 0x3f;
45 hash_num >>= 6;
46 result += base64_chars[sextet];
47 }
48 return result;
49}
50
51
Mohamed Heikal525714a2019-07-18 11:14:31 -040052// Return the optimal hash length such that at most 10% of resources collide in
53// their shortened path.
Mohamed Heikalc7694032018-11-07 16:49:02 -050054// Reference: http://matt.might.net/articles/counting-hash-collisions/
55int OptimalShortenedLength(int num_resources) {
Mohamed Heikal525714a2019-07-18 11:14:31 -040056 if (num_resources > 4000) {
57 return 3;
58 } else {
59 return 2;
Mohamed Heikalc7694032018-11-07 16:49:02 -050060 }
Mohamed Heikalc7694032018-11-07 16:49:02 -050061}
62
63std::string GetShortenedPath(const android::StringPiece& shortened_filename,
64 const android::StringPiece& extension, int collision_count) {
65 std::string shortened_path = "res/" + shortened_filename.to_string();
66 if (collision_count > 0) {
67 shortened_path += std::to_string(collision_count);
68 }
69 shortened_path += extension;
70 return shortened_path;
71}
72
73bool ResourcePathShortener::Consume(IAaptContext* context, ResourceTable* table) {
74 // used to detect collisions
75 std::unordered_set<std::string> shortened_paths;
76 std::unordered_set<FileReference*> file_refs;
77 for (auto& package : table->packages) {
78 for (auto& type : package->types) {
79 for (auto& entry : type->entries) {
80 for (auto& config_value : entry->values) {
81 FileReference* file_ref = ValueCast<FileReference>(config_value->value.get());
82 if (file_ref) {
83 file_refs.insert(file_ref);
84 }
85 }
86 }
87 }
88 }
89 int num_chars = OptimalShortenedLength(file_refs.size());
90 for (auto& file_ref : file_refs) {
91 android::StringPiece res_subdir, actual_filename, extension;
92 util::ExtractResFilePathParts(*file_ref->path, &res_subdir, &actual_filename, &extension);
93
Mohamed Heikal7c757302019-04-25 17:39:43 -040094 // Android detects ColorStateLists via pathname, skip res/color/*
95 if (res_subdir == android::StringPiece("res/color/"))
96 continue;
97
Mohamed Heikalc7694032018-11-07 16:49:02 -050098 std::string shortened_filename = ShortenFileName(*file_ref->path, num_chars);
99 int collision_count = 0;
100 std::string shortened_path = GetShortenedPath(shortened_filename, extension, collision_count);
101 while (shortened_paths.find(shortened_path) != shortened_paths.end()) {
102 collision_count++;
103 shortened_path = GetShortenedPath(shortened_filename, extension, collision_count);
104 }
105 shortened_paths.insert(shortened_path);
106 path_map_.insert({*file_ref->path, shortened_path});
107 file_ref->path = table->string_pool.MakeRef(shortened_path, file_ref->path.GetContext());
108 }
109 return true;
110}
111
112} // namespace aapt