AAPT2: Embed version of framework an app was compiled against.

This change injects the 'android:compileSdkVersion' and
'android:compileSdkVersionCodename' into an app's AndroidManifest.xml,
whose values are dervied from the version name and version code of the
framework AndroidManifest.xml that is being linked against.

Bug: 63388434
Test: make aapt2_tests
Change-Id: I7b607192ecb337307c4bcb5770e7f716c6d4c9d2
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index d2aebfd..13dd93e 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <sys/stat.h>
+#include <cinttypes>
 
 #include <queue>
 #include <unordered_map>
@@ -725,6 +726,30 @@
   return true;
 }
 
+static int32_t FindFrameworkAssetManagerCookie(const android::AssetManager& assets) {
+  using namespace android;
+
+  // Find the system package (0x01). AAPT always generates attributes with the type 0x01, so
+  // we're looking for the first attribute resource in the system package.
+  const ResTable& table = assets.getResources(true);
+  Res_value val;
+  ssize_t idx = table.getResource(0x01010000, &val, true);
+  if (idx != NO_ERROR) {
+    // Try as a bag.
+    const ResTable::bag_entry* entry;
+    ssize_t cnt = table.lockBag(0x01010000, &entry);
+    if (cnt >= 0) {
+      idx = entry->stringBlock;
+    }
+    table.unlockBag(entry);
+  }
+
+  if (idx < 0) {
+    return 0;
+  }
+  return table.getTableCookie(idx);
+}
+
 class LinkCommand {
  public:
   LinkCommand(LinkContext* context, const LinkOptions& options)
@@ -734,7 +759,65 @@
         file_collection_(util::make_unique<io::FileCollection>()) {
   }
 
+  void ExtractCompileSdkVersions(android::AssetManager* assets) {
+    using namespace android;
+
+    int32_t cookie = FindFrameworkAssetManagerCookie(*assets);
+    if (cookie == 0) {
+      // No Framework assets loaded. Not a failure.
+      return;
+    }
+
+    std::unique_ptr<Asset> manifest(
+        assets->openNonAsset(cookie, kAndroidManifestPath, Asset::AccessMode::ACCESS_BUFFER));
+    if (manifest == nullptr) {
+      // No errors.
+      return;
+    }
+
+    std::string error;
+    std::unique_ptr<xml::XmlResource> manifest_xml =
+        xml::Inflate(manifest->getBuffer(true /*wordAligned*/), manifest->getLength(), &error);
+    if (manifest_xml == nullptr) {
+      // No errors.
+      return;
+    }
+
+    xml::Attribute* attr = manifest_xml->root->FindAttribute(xml::kSchemaAndroid, "versionCode");
+    if (attr != nullptr) {
+      Maybe<std::string>& compile_sdk_version = options_.manifest_fixer_options.compile_sdk_version;
+      if (BinaryPrimitive* prim = ValueCast<BinaryPrimitive>(attr->compiled_value.get())) {
+        switch (prim->value.dataType) {
+          case Res_value::TYPE_INT_DEC:
+            compile_sdk_version = StringPrintf("%" PRId32, static_cast<int32_t>(prim->value.data));
+            break;
+          case Res_value::TYPE_INT_HEX:
+            compile_sdk_version = StringPrintf("%" PRIx32, prim->value.data);
+            break;
+          default:
+            break;
+        }
+      } else if (String* str = ValueCast<String>(attr->compiled_value.get())) {
+        compile_sdk_version = *str->value;
+      } else {
+        compile_sdk_version = attr->value;
+      }
+    }
+
+    attr = manifest_xml->root->FindAttribute(xml::kSchemaAndroid, "versionName");
+    if (attr != nullptr) {
+      Maybe<std::string>& compile_sdk_version_codename =
+          options_.manifest_fixer_options.compile_sdk_version_codename;
+      if (String* str = ValueCast<String>(attr->compiled_value.get())) {
+        compile_sdk_version_codename = *str->value;
+      } else {
+        compile_sdk_version_codename = attr->value;
+      }
+    }
+  }
+
   // Creates a SymbolTable that loads symbols from the various APKs.
+  // Pre-condition: context_->GetCompilationPackage() needs to be set.
   bool LoadSymbolsFromIncludePaths() {
     auto asset_source = util::make_unique<AssetManagerSymbolSource>();
     for (const std::string& path : options_.include_paths) {
@@ -802,6 +885,17 @@
       } else if (entry.first == kAppPackageId) {
         // Capture the included base feature package.
         included_feature_base_ = entry.second;
+      } else if (entry.first == kFrameworkPackageId) {
+        // Try to embed which version of the framework we're compiling against.
+        // First check if we should use compileSdkVersion at all. Otherwise compilation may fail
+        // when linking our synthesized 'android:compileSdkVersion' attribute.
+        std::unique_ptr<SymbolTable::Symbol> symbol = asset_source->FindByName(
+            ResourceName("android", ResourceType::kAttr, "compileSdkVersion"));
+        if (symbol != nullptr && symbol->is_public) {
+          // The symbol is present and public, extract the android:versionName and
+          // android:versionCode from the framework AndroidManifest.xml.
+          ExtractCompileSdkVersions(asset_source->GetAssetManager());
+        }
       }
     }
 
@@ -1535,6 +1629,12 @@
       context_->SetCompilationPackage(app_info.package);
     }
 
+    // Now that the compilation package is set, load the dependencies. This will also extract
+    // the Android framework's versionCode and versionName, if they exist.
+    if (!LoadSymbolsFromIncludePaths()) {
+      return 1;
+    }
+
     ManifestFixer manifest_fixer(options_.manifest_fixer_options);
     if (!manifest_fixer.Consume(context_, manifest_xml.get())) {
       return 1;
@@ -1563,10 +1663,6 @@
       }
     }
 
-    if (!LoadSymbolsFromIncludePaths()) {
-      return 1;
-    }
-
     TableMergerOptions table_merger_options;
     table_merger_options.auto_add_overlay = options_.auto_add_overlay;
     table_merger_ = util::make_unique<TableMerger>(context_, &final_table_, table_merger_options);
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index eaaefd5..a68df1d 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -406,6 +406,25 @@
     root->InsertChild(0, std::move(uses_sdk));
   }
 
+  if (options_.compile_sdk_version) {
+    xml::Attribute* attr = root->FindOrCreateAttribute(xml::kSchemaAndroid, "compileSdkVersion");
+
+    // Make sure we un-compile the value if it was set to something else.
+    attr->compiled_value = {};
+
+    attr->value = options_.compile_sdk_version.value();
+  }
+
+  if (options_.compile_sdk_version_codename) {
+    xml::Attribute* attr =
+        root->FindOrCreateAttribute(xml::kSchemaAndroid, "compileSdkVersionCodename");
+
+    // Make sure we un-compile the value if it was set to something else.
+    attr->compiled_value = {};
+
+    attr->value = options_.compile_sdk_version_codename.value();
+  }
+
   xml::XmlActionExecutor executor;
   if (!BuildRules(&executor, context->GetDiagnostics())) {
     return false;
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index 470f65e..f5715f6 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -29,22 +29,42 @@
 namespace aapt {
 
 struct ManifestFixerOptions {
+  // The minimum SDK version to set if no 'android:minSdkVersion' is defined in a <uses-sdk> tag.
   Maybe<std::string> min_sdk_version_default;
+
+  // The target SDK version to set if no 'android:targetSdkVersion' is defined in a <uses-sdk> tag.
   Maybe<std::string> target_sdk_version_default;
+
+  // The Android package to use instead of the one defined in 'package' in <manifest>.
+  // This also renames all relative package/class names in the manifest to fully qualified
+  // Java names.
   Maybe<std::string> rename_manifest_package;
+
+  // The Android package to use instead of the one defined in 'android:targetPackage' in
+  // <instrumentation>.
   Maybe<std::string> rename_instrumentation_target_package;
+
+  // The version name to set if 'android:versionName' is not defined in <manifest>.
   Maybe<std::string> version_name_default;
+
+  // The version code to set if 'android:versionCode' is not defined in <manifest>.
   Maybe<std::string> version_code_default;
+
+  // The version of the framework being compiled against to set for 'android:compileSdkVersion' in
+  // the <manifest> tag.
+  Maybe<std::string> compile_sdk_version;
+
+  // The version codename of the framework being compiled against to set for
+  // 'android:compileSdkVersionCodename' in the <manifest> tag.
+  Maybe<std::string> compile_sdk_version_codename;
 };
 
-/**
- * Verifies that the manifest is correctly formed and inserts defaults
- * where specified with ManifestFixerOptions.
- */
+// Verifies that the manifest is correctly formed and inserts defaults where specified with
+// ManifestFixerOptions.
 class ManifestFixer : public IXmlResourceConsumer {
  public:
-  explicit ManifestFixer(const ManifestFixerOptions& options)
-      : options_(options) {}
+  explicit ManifestFixer(const ManifestFixerOptions& options) : options_(options) {
+  }
 
   bool Consume(IAaptContext* context, xml::XmlResource* doc) override;
 
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 40085ea..1320dcd 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -19,7 +19,12 @@
 #include "test/Test.h"
 
 using ::android::StringPiece;
+using ::testing::Eq;
+using ::testing::Gt;
+using ::testing::IsNull;
+using ::testing::Ne;
 using ::testing::NotNull;
+using ::testing::StrEq;
 
 namespace aapt {
 
@@ -72,22 +77,20 @@
 };
 
 TEST_F(ManifestFixerTest, EnsureManifestIsRootTag) {
-  EXPECT_EQ(nullptr, Verify("<other-tag />"));
-  EXPECT_EQ(nullptr, Verify("<ns:manifest xmlns:ns=\"com\" />"));
-  EXPECT_NE(nullptr, Verify("<manifest package=\"android\"></manifest>"));
+  EXPECT_THAT(Verify("<other-tag />"), IsNull());
+  EXPECT_THAT(Verify("<ns:manifest xmlns:ns=\"com\" />"), IsNull());
+  EXPECT_THAT(Verify("<manifest package=\"android\"></manifest>"), NotNull());
 }
 
 TEST_F(ManifestFixerTest, EnsureManifestHasPackage) {
-  EXPECT_NE(nullptr, Verify("<manifest package=\"android\" />"));
-  EXPECT_NE(nullptr, Verify("<manifest package=\"com.android\" />"));
-  EXPECT_NE(nullptr, Verify("<manifest package=\"com.android.google\" />"));
-  EXPECT_EQ(nullptr,
-            Verify("<manifest package=\"com.android.google.Class$1\" />"));
-  EXPECT_EQ(nullptr, Verify("<manifest "
-                            "xmlns:android=\"http://schemas.android.com/apk/"
-                            "res/android\" "
-                            "android:package=\"com.android\" />"));
-  EXPECT_EQ(nullptr, Verify("<manifest package=\"@string/str\" />"));
+  EXPECT_THAT(Verify("<manifest package=\"android\" />"), NotNull());
+  EXPECT_THAT(Verify("<manifest package=\"com.android\" />"), NotNull());
+  EXPECT_THAT(Verify("<manifest package=\"com.android.google\" />"), NotNull());
+  EXPECT_THAT(Verify("<manifest package=\"com.android.google.Class$1\" />"), IsNull());
+  EXPECT_THAT(Verify("<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" "
+                     "android:package=\"com.android\" />"),
+              IsNull());
+  EXPECT_THAT(Verify("<manifest package=\"@string/str\" />"), IsNull());
 }
 
 TEST_F(ManifestFixerTest, AllowMetaData) {
@@ -105,7 +108,7 @@
           </application>
           <instrumentation android:name=".Go"><meta-data /></instrumentation>
         </manifest>)EOF");
-  ASSERT_NE(nullptr, doc);
+  ASSERT_THAT(doc, NotNull());
 }
 
 TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) {
@@ -117,21 +120,21 @@
         <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" />
       </manifest>)EOF",
                                                             options);
-  ASSERT_NE(nullptr, doc);
+  ASSERT_THAT(doc, NotNull());
 
   xml::Element* el;
   xml::Attribute* attr;
 
   el = doc->root.get();
-  ASSERT_NE(nullptr, el);
+  ASSERT_THAT(el, NotNull());
   el = el->FindChild({}, "uses-sdk");
-  ASSERT_NE(nullptr, el);
+  ASSERT_THAT(el, NotNull());
   attr = el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion");
-  ASSERT_NE(nullptr, attr);
-  EXPECT_EQ("7", attr->value);
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("7"));
   attr = el->FindAttribute(xml::kSchemaAndroid, "targetSdkVersion");
-  ASSERT_NE(nullptr, attr);
-  EXPECT_EQ("21", attr->value);
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("21"));
 
   doc = VerifyWithOptions(R"EOF(
       <manifest xmlns:android="http://schemas.android.com/apk/res/android"
@@ -139,18 +142,18 @@
         <uses-sdk android:targetSdkVersion="21" />
       </manifest>)EOF",
                           options);
-  ASSERT_NE(nullptr, doc);
+  ASSERT_THAT(doc, NotNull());
 
   el = doc->root.get();
-  ASSERT_NE(nullptr, el);
+  ASSERT_THAT(el, NotNull());
   el = el->FindChild({}, "uses-sdk");
-  ASSERT_NE(nullptr, el);
+  ASSERT_THAT(el, NotNull());
   attr = el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion");
-  ASSERT_NE(nullptr, attr);
-  EXPECT_EQ("8", attr->value);
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("8"));
   attr = el->FindAttribute(xml::kSchemaAndroid, "targetSdkVersion");
-  ASSERT_NE(nullptr, attr);
-  EXPECT_EQ("21", attr->value);
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("21"));
 
   doc = VerifyWithOptions(R"EOF(
       <manifest xmlns:android="http://schemas.android.com/apk/res/android"
@@ -158,35 +161,35 @@
         <uses-sdk />
       </manifest>)EOF",
                           options);
-  ASSERT_NE(nullptr, doc);
+  ASSERT_THAT(doc, NotNull());
 
   el = doc->root.get();
-  ASSERT_NE(nullptr, el);
+  ASSERT_THAT(el, NotNull());
   el = el->FindChild({}, "uses-sdk");
-  ASSERT_NE(nullptr, el);
+  ASSERT_THAT(el, NotNull());
   attr = el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion");
-  ASSERT_NE(nullptr, attr);
-  EXPECT_EQ("8", attr->value);
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("8"));
   attr = el->FindAttribute(xml::kSchemaAndroid, "targetSdkVersion");
-  ASSERT_NE(nullptr, attr);
-  EXPECT_EQ("22", attr->value);
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("22"));
 
   doc = VerifyWithOptions(R"EOF(
       <manifest xmlns:android="http://schemas.android.com/apk/res/android"
                 package="android" />)EOF",
                           options);
-  ASSERT_NE(nullptr, doc);
+  ASSERT_THAT(doc, NotNull());
 
   el = doc->root.get();
-  ASSERT_NE(nullptr, el);
+  ASSERT_THAT(el, NotNull());
   el = el->FindChild({}, "uses-sdk");
-  ASSERT_NE(nullptr, el);
+  ASSERT_THAT(el, NotNull());
   attr = el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion");
-  ASSERT_NE(nullptr, attr);
-  EXPECT_EQ("8", attr->value);
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("8"));
   attr = el->FindAttribute(xml::kSchemaAndroid, "targetSdkVersion");
-  ASSERT_NE(nullptr, attr);
-  EXPECT_EQ("22", attr->value);
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("22"));
 }
 
 TEST_F(ManifestFixerTest, UsesSdkMustComeBeforeApplication) {
@@ -197,17 +200,17 @@
             <application android:name=".MainApplication" />
           </manifest>)EOF",
                                                             options);
-  ASSERT_NE(nullptr, doc);
+  ASSERT_THAT(doc, NotNull());
 
   xml::Element* manifest_el = doc->root.get();
-  ASSERT_NE(nullptr, manifest_el);
+  ASSERT_THAT(manifest_el, NotNull());
   ASSERT_EQ("manifest", manifest_el->name);
 
   xml::Element* application_el = manifest_el->FindChild("", "application");
-  ASSERT_NE(nullptr, application_el);
+  ASSERT_THAT(application_el, NotNull());
 
   xml::Element* uses_sdk_el = manifest_el->FindChild("", "uses-sdk");
-  ASSERT_NE(nullptr, uses_sdk_el);
+  ASSERT_THAT(uses_sdk_el, NotNull());
 
   // Check that the uses_sdk_el comes before application_el in the children
   // vector.
@@ -225,12 +228,12 @@
                      return child.get() == application_el;
                    });
 
-  ASSERT_NE(manifest_el->children.end(), uses_sdk_iter);
-  ASSERT_NE(manifest_el->children.end(), application_iter);
+  ASSERT_THAT(uses_sdk_iter, Ne(manifest_el->children.end()));
+  ASSERT_THAT(application_iter, Ne(manifest_el->children.end()));
 
   // The distance should be positive, meaning uses_sdk_iter comes before
   // application_iter.
-  EXPECT_GT(std::distance(uses_sdk_iter, application_iter), 0);
+  EXPECT_THAT(std::distance(uses_sdk_iter, application_iter), Gt(0));
 }
 
 TEST_F(ManifestFixerTest, RenameManifestPackageAndFullyQualifyClasses) {
@@ -247,48 +250,49 @@
         </application>
       </manifest>)EOF",
                                                             options);
-  ASSERT_NE(nullptr, doc);
+  ASSERT_THAT(doc, NotNull());
 
   xml::Element* manifest_el = doc->root.get();
-  ASSERT_NE(nullptr, manifest_el);
+  ASSERT_THAT(manifest_el, NotNull());
 
   xml::Attribute* attr = nullptr;
 
   attr = manifest_el->FindAttribute({}, "package");
-  ASSERT_NE(nullptr, attr);
-  EXPECT_EQ(std::string("com.android"), attr->value);
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("com.android"));
 
   xml::Element* uses_split_el = manifest_el->FindChild({}, "uses-split");
-  ASSERT_NE(nullptr, uses_split_el);
+  ASSERT_THAT(uses_split_el, NotNull());
   attr = uses_split_el->FindAttribute(xml::kSchemaAndroid, "name");
-  ASSERT_NE(nullptr, attr);
-  EXPECT_EQ(std::string("feature_a"), attr->value);
+  ASSERT_THAT(attr, NotNull());
+  // This should NOT have been affected.
+  EXPECT_THAT(attr->value, StrEq("feature_a"));
 
   xml::Element* application_el = manifest_el->FindChild({}, "application");
-  ASSERT_NE(nullptr, application_el);
+  ASSERT_THAT(application_el, NotNull());
 
   attr = application_el->FindAttribute(xml::kSchemaAndroid, "name");
-  ASSERT_NE(nullptr, attr);
-  EXPECT_EQ(std::string("android.MainApplication"), attr->value);
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("android.MainApplication"));
 
   attr = application_el->FindAttribute({}, "text");
-  ASSERT_NE(nullptr, attr);
-  EXPECT_EQ(std::string("hello"), attr->value);
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("hello"));
 
   xml::Element* el;
   el = application_el->FindChild({}, "activity");
-  ASSERT_NE(nullptr, el);
+  ASSERT_THAT(el, NotNull());
 
   attr = el->FindAttribute(xml::kSchemaAndroid, "name");
-  ASSERT_NE(nullptr, el);
-  EXPECT_EQ(std::string("android.activity.Start"), attr->value);
+  ASSERT_THAT(el, NotNull());
+  EXPECT_THAT(attr->value, StrEq("android.activity.Start"));
 
   el = application_el->FindChild({}, "receiver");
-  ASSERT_NE(nullptr, el);
+  ASSERT_THAT(el, NotNull());
 
   attr = el->FindAttribute(xml::kSchemaAndroid, "name");
-  ASSERT_NE(nullptr, el);
-  EXPECT_EQ(std::string("com.google.android.Receiver"), attr->value);
+  ASSERT_THAT(el, NotNull());
+  EXPECT_THAT(attr->value, StrEq("com.google.android.Receiver"));
 }
 
 TEST_F(ManifestFixerTest,
@@ -302,19 +306,19 @@
         <instrumentation android:name=".TestRunner" android:targetPackage="android" />
       </manifest>)EOF",
                                                             options);
-  ASSERT_NE(nullptr, doc);
+  ASSERT_THAT(doc, NotNull());
 
   xml::Element* manifest_el = doc->root.get();
-  ASSERT_NE(nullptr, manifest_el);
+  ASSERT_THAT(manifest_el, NotNull());
 
   xml::Element* instrumentation_el =
       manifest_el->FindChild({}, "instrumentation");
-  ASSERT_NE(nullptr, instrumentation_el);
+  ASSERT_THAT(instrumentation_el, NotNull());
 
   xml::Attribute* attr =
       instrumentation_el->FindAttribute(xml::kSchemaAndroid, "targetPackage");
-  ASSERT_NE(nullptr, attr);
-  EXPECT_EQ(std::string("com.android"), attr->value);
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("com.android"));
 }
 
 TEST_F(ManifestFixerTest, UseDefaultVersionNameAndCode) {
@@ -326,41 +330,39 @@
       <manifest xmlns:android="http://schemas.android.com/apk/res/android"
                 package="android" />)EOF",
                                                             options);
-  ASSERT_NE(nullptr, doc);
+  ASSERT_THAT(doc, NotNull());
 
   xml::Element* manifest_el = doc->root.get();
-  ASSERT_NE(nullptr, manifest_el);
+  ASSERT_THAT(manifest_el, NotNull());
 
   xml::Attribute* attr =
       manifest_el->FindAttribute(xml::kSchemaAndroid, "versionName");
-  ASSERT_NE(nullptr, attr);
-  EXPECT_EQ(std::string("Beta"), attr->value);
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("Beta"));
 
   attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode");
-  ASSERT_NE(nullptr, attr);
-  EXPECT_EQ(std::string("0x10000000"), attr->value);
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("0x10000000"));
 }
 
 TEST_F(ManifestFixerTest, EnsureManifestAttributesAreTyped) {
-  EXPECT_EQ(nullptr,
-            Verify("<manifest package=\"android\" coreApp=\"hello\" />"));
-  EXPECT_EQ(nullptr,
-            Verify("<manifest package=\"android\" coreApp=\"1dp\" />"));
+  EXPECT_THAT(Verify("<manifest package=\"android\" coreApp=\"hello\" />"), IsNull());
+  EXPECT_THAT(Verify("<manifest package=\"android\" coreApp=\"1dp\" />"), IsNull());
 
   std::unique_ptr<xml::XmlResource> doc =
       Verify("<manifest package=\"android\" coreApp=\"true\" />");
-  ASSERT_NE(nullptr, doc);
+  ASSERT_THAT(doc, NotNull());
 
   xml::Element* el = doc->root.get();
-  ASSERT_NE(nullptr, el);
+  ASSERT_THAT(el, NotNull());
 
-  EXPECT_EQ("manifest", el->name);
+  EXPECT_THAT(el->name, StrEq("manifest"));
 
   xml::Attribute* attr = el->FindAttribute("", "coreApp");
-  ASSERT_NE(nullptr, attr);
+  ASSERT_THAT(attr, NotNull());
 
-  EXPECT_NE(nullptr, attr->compiled_value);
-  EXPECT_NE(nullptr, ValueCast<BinaryPrimitive>(attr->compiled_value.get()));
+  EXPECT_THAT(attr->compiled_value, NotNull());
+  EXPECT_THAT(ValueCast<BinaryPrimitive>(attr->compiled_value.get()), NotNull());
 }
 
 TEST_F(ManifestFixerTest, UsesFeatureMustHaveNameOrGlEsVersion) {
@@ -375,21 +377,21 @@
             <uses-feature android:glEsVersion="2" />
           </feature-group>
         </manifest>)EOF";
-  EXPECT_NE(nullptr, Verify(input));
+  EXPECT_THAT(Verify(input), NotNull());
 
   input = R"EOF(
         <manifest xmlns:android="http://schemas.android.com/apk/res/android"
                   package="android">
           <uses-feature android:name="feature" android:glEsVersion="1" />
         </manifest>)EOF";
-  EXPECT_EQ(nullptr, Verify(input));
+  EXPECT_THAT(Verify(input), IsNull());
 
   input = R"EOF(
         <manifest xmlns:android="http://schemas.android.com/apk/res/android"
                   package="android">
           <uses-feature />
         </manifest>)EOF";
-  EXPECT_EQ(nullptr, Verify(input));
+  EXPECT_THAT(Verify(input), IsNull());
 
   input = R"EOF(
         <manifest xmlns:android="http://schemas.android.com/apk/res/android"
@@ -398,7 +400,7 @@
             <uses-feature android:name="feature" android:glEsVersion="1" />
           </feature-group>
         </manifest>)EOF";
-  EXPECT_EQ(nullptr, Verify(input));
+  EXPECT_THAT(Verify(input), IsNull());
 
   input = R"EOF(
         <manifest xmlns:android="http://schemas.android.com/apk/res/android"
@@ -407,7 +409,7 @@
             <uses-feature />
           </feature-group>
         </manifest>)EOF";
-  EXPECT_EQ(nullptr, Verify(input));
+  EXPECT_THAT(Verify(input), IsNull());
 }
 
 TEST_F(ManifestFixerTest, IgnoreNamespacedElements) {
@@ -416,7 +418,7 @@
                 package="android">
         <special:tag whoo="true" xmlns:special="http://google.com" />
       </manifest>)EOF";
-  EXPECT_NE(nullptr, Verify(input));
+  EXPECT_THAT(Verify(input), NotNull());
 }
 
 TEST_F(ManifestFixerTest, DoNotIgnoreNonNamespacedElements) {
@@ -425,7 +427,7 @@
                 package="android">
         <tag whoo="true" />
       </manifest>)EOF";
-  EXPECT_EQ(nullptr, Verify(input));
+  EXPECT_THAT(Verify(input), IsNull());
 }
 
 TEST_F(ManifestFixerTest, SupportKeySets) {
@@ -446,4 +448,23 @@
   EXPECT_THAT(Verify(input), NotNull());
 }
 
+TEST_F(ManifestFixerTest, InsertCompileSdkVersions) {
+  std::string input = R"(
+      <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android" />)";
+  ManifestFixerOptions options;
+  options.compile_sdk_version = {"28"};
+  options.compile_sdk_version_codename = {"P"};
+
+  std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options);
+  ASSERT_THAT(manifest, NotNull());
+
+  xml::Attribute* attr = manifest->root->FindAttribute(xml::kSchemaAndroid, "compileSdkVersion");
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("28"));
+
+  attr = manifest->root->FindAttribute(xml::kSchemaAndroid, "compileSdkVersionCodename");
+  ASSERT_THAT(attr, NotNull());
+  EXPECT_THAT(attr->value, StrEq("P"));
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
index 4a2181e..b676efb 100644
--- a/tools/aapt2/process/SymbolTable.h
+++ b/tools/aapt2/process/SymbolTable.h
@@ -199,6 +199,10 @@
   std::unique_ptr<SymbolTable::Symbol> FindByReference(
       const Reference& ref) override;
 
+  android::AssetManager* GetAssetManager() {
+    return &assets_;
+  }
+
  private:
   android::AssetManager assets_;
 
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index b0cf44a..fddb6b8 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -418,6 +418,15 @@
   return nullptr;
 }
 
+Attribute* Element::FindOrCreateAttribute(const StringPiece& ns, const StringPiece& name) {
+  Attribute* attr = FindAttribute(ns, name);
+  if (attr == nullptr) {
+    attributes.push_back(Attribute{ns.to_string(), name.to_string()});
+    attr = &attributes.back();
+  }
+  return attr;
+}
+
 Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) {
   return FindChildWithAttribute(ns, name, {}, {}, {});
 }
diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h
index cf06ba5..8f382961 100644
--- a/tools/aapt2/xml/XmlDom.h
+++ b/tools/aapt2/xml/XmlDom.h
@@ -100,6 +100,8 @@
   Attribute* FindAttribute(const android::StringPiece& ns, const android::StringPiece& name);
   const Attribute* FindAttribute(const android::StringPiece& ns,
                                  const android::StringPiece& name) const;
+  Attribute* FindOrCreateAttribute(const android::StringPiece& ns,
+                                   const android::StringPiece& name);
 
   Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name);
   const Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name) const;