Files
frameworks_base/tools/aapt2/format/binary/ResEntryWriter_test.cpp
Eric Miao 368cd19d03 androidfw: Add support for compact resource entries
Bug: 237583012

Given the large number of simple resources such as strings in
Android resources, their ResTable_entry and Res_value can be
encoded together in a compact way.  This allows a significant
saving in both storage and memory footprint.

The basic observations for simple resources are:

* ResTable_entry.size will always be sizeof(ResTable_entry)
  unless it's a complex entry

* ResTable_entry.key is unlikely to exceed 16-bit

* ResTable_entry.flags only uses 3 bits for now

* Res_value.size will always be sizeof(Res_value)

Given the above, we could well encode the information into
a compact/compatible structure.

  struct compact {
      uint16_t key;
      uint16_t flags;
      uint32_t data;
  };

The layout of this structure will allow maximum backward
compatibility. e.g. the flags will be at the same offset,
and a

  `dtohs((ResTable_entry *)entry->flags) & FLAG_COMPACT`

would tell if this entry is a compact one or not. For a
compact entry:

  struct compact *entry;

  entry_size  == sizeof(*entry)
  entry_key   == static_cast<uint32_t>(dtohs(entry->key))
  entry_flags == dtohs(entry->flags) & 0xff	// low 8-bit
  data_type   == dtohs(entry->flags) >> 8	// high 8-bit
  data_size   == sizeof(Res_value)
  data_value  == dtohl(entry->data)

To allow minimum code change and backward compatibility,
we change 'struct ResTable_entry' to 'union ResTable_entry',
with an anonymous structure inside that's fully backward
compatible. Thus, any existing reference such as:

  ResTable_entry *entry = ...
  if (dtohs(entry->flags) & ResTable_entry::FLAG_COMPLEX) ...

would still work.

However, special care needs to be taken after an entry is
obtained, and when value needs to be extracted.

A compact entry will not encode a complex value, and hence
complex entries/values are handled the same way.

Change-Id: I15d97a4f5e85fab28c075496f7f0cf6b1fcd73e3
2022-11-15 12:12:33 -08:00

191 lines
7.3 KiB
C++

/*
* Copyright (C) 2022 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 "format/binary/ResEntryWriter.h"
#include "androidfw/BigBuffer.h"
#include "format/binary/ResourceTypeExtensions.h"
#include "test/Test.h"
#include "util/Util.h"
using ::android::BigBuffer;
using ::android::Res_value;
using ::android::ResTable_map;
using ::testing::Eq;
using ::testing::Ge;
using ::testing::IsNull;
using ::testing::Ne;
using ::testing::NotNull;
namespace aapt {
using SequentialResEntryWriterTest = CommandTestFixture;
using DeduplicateItemsResEntryWriterTest = CommandTestFixture;
std::vector<int32_t> WriteAllEntries(const ResourceTableView& table, ResEntryWriter& writer) {
std::vector<int32_t> result = {};
for (const auto& type : table.packages[0].types) {
for (const auto& entry : type.entries) {
for (const auto& value : entry.values) {
auto flat_entry = FlatEntry{&entry, value->value.get(), 0};
result.push_back(writer.Write(&flat_entry));
}
}
}
return result;
}
TEST_F(SequentialResEntryWriterTest, WriteEntriesOneByOne) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
.AddSimple("com.app.test:id/id1", ResourceId(0x7f010000))
.AddSimple("com.app.test:id/id2", ResourceId(0x7f010001))
.AddSimple("com.app.test:id/id3", ResourceId(0x7f010002))
.Build();
{
BigBuffer out(512);
SequentialResEntryWriter<false> writer(&out);
auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
std::vector<int32_t> expected_offsets{0, sizeof(ResEntryValuePair),
2 * sizeof(ResEntryValuePair)};
EXPECT_EQ(out.size(), 3 * sizeof(ResEntryValuePair));
EXPECT_EQ(offsets, expected_offsets);
}
{
/* expect a compact entry to only take sizeof(ResTable_entry) */
BigBuffer out(512);
SequentialResEntryWriter<true> writer(&out);
auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry),
2 * sizeof(ResTable_entry)};
EXPECT_EQ(out.size(), 3 * sizeof(ResTable_entry));
EXPECT_EQ(offsets, expected_offsets);
}
};
TEST_F(SequentialResEntryWriterTest, WriteMapEntriesOneByOne) {
std::unique_ptr<Array> array1 = util::make_unique<Array>();
array1->elements.push_back(
util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u));
array1->elements.push_back(
util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u));
std::unique_ptr<Array> array2 = util::make_unique<Array>();
array2->elements.push_back(
util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u));
array2->elements.push_back(
util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u));
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
.AddValue("com.app.test:array/arr1", std::move(array1))
.AddValue("com.app.test:array/arr2", std::move(array2))
.Build();
{
BigBuffer out(512);
SequentialResEntryWriter<false> writer(&out);
auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
EXPECT_EQ(offsets, expected_offsets);
}
{
/* compact_entry should have no impact to map items */
BigBuffer out(512);
SequentialResEntryWriter<true> writer(&out);
auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
EXPECT_EQ(offsets, expected_offsets);
}
};
TEST_F(DeduplicateItemsResEntryWriterTest, DeduplicateItemEntries) {
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
.AddSimple("com.app.test:id/id1", ResourceId(0x7f010000))
.AddSimple("com.app.test:id/id2", ResourceId(0x7f010001))
.AddSimple("com.app.test:id/id3", ResourceId(0x7f010002))
.Build();
{
BigBuffer out(512);
DeduplicateItemsResEntryWriter<false> writer(&out);
auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
std::vector<int32_t> expected_offsets{0, 0, 0};
EXPECT_EQ(out.size(), sizeof(ResEntryValuePair));
EXPECT_EQ(offsets, expected_offsets);
}
{
/* expect a compact entry to only take sizeof(ResTable_entry) */
BigBuffer out(512);
DeduplicateItemsResEntryWriter<true> writer(&out);
auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
std::vector<int32_t> expected_offsets{0, 0, 0};
EXPECT_EQ(out.size(), sizeof(ResTable_entry));
EXPECT_EQ(offsets, expected_offsets);
}
};
TEST_F(DeduplicateItemsResEntryWriterTest, WriteMapEntriesOneByOne) {
std::unique_ptr<Array> array1 = util::make_unique<Array>();
array1->elements.push_back(
util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u));
array1->elements.push_back(
util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u));
std::unique_ptr<Array> array2 = util::make_unique<Array>();
array2->elements.push_back(
util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u));
array2->elements.push_back(
util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u));
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
.AddValue("com.app.test:array/arr1", std::move(array1))
.AddValue("com.app.test:array/arr2", std::move(array2))
.Build();
{
BigBuffer out(512);
DeduplicateItemsResEntryWriter<false> writer(&out);
auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
EXPECT_EQ(offsets, expected_offsets);
}
{
/* compact_entry should have no impact to map items */
BigBuffer out(512);
DeduplicateItemsResEntryWriter<true> writer(&out);
auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
EXPECT_EQ(offsets, expected_offsets);
}
};
} // namespace aapt