Alias v26 round icons to adaptive icons like aapt1

When a developer specifies an adaptive application icon,
and a non-adaptive round application icon, create an alias
from the round icon to the regular icon for v26 APIs and up.

We do this because certain devices prefer android:roundIcon over
android:icon regardless of the API levels of the drawables set for
either.

This auto-aliasing behaviour allows an app to prefer the
android:roundIcon on API 25 devices, and prefer the adaptive icon on
API 26 devices.

Bug: 34829129
Test: manual
Change-Id: Ifac49455da749afde636fd4bb8cf32308f8b1e0a
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index f354bb6..3a9a050 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -1565,6 +1565,93 @@
     return true;
   }
 
+  void AliasAdaptiveIcon(xml::XmlResource* manifest, ResourceTable* table) {
+    xml::Element* application = manifest->root->FindChild("", "application");
+    if (!application) {
+      return;
+    }
+
+    xml::Attribute* icon = application->FindAttribute(xml::kSchemaAndroid, "icon");
+    xml::Attribute* round_icon = application->FindAttribute(xml::kSchemaAndroid, "roundIcon");
+    if (!icon || !round_icon) {
+      return;
+    }
+
+    // Find the icon resource defined within the application.
+    auto icon_reference = ValueCast<Reference>(icon->compiled_value.get());
+    if (!icon_reference || !icon_reference->name) {
+      return;
+    }
+    auto package = table->FindPackageById(icon_reference->id.value().package_id());
+    if (!package) {
+      return;
+    }
+    auto type = package->FindType(icon_reference->name.value().type);
+    if (!type) {
+      return;
+    }
+    auto icon_entry = type->FindEntry(icon_reference->name.value().entry);
+    if (!icon_entry) {
+      return;
+    }
+
+    int icon_max_sdk = 0;
+    for (auto& config_value : icon_entry->values) {
+      icon_max_sdk = (icon_max_sdk < config_value->config.sdkVersion)
+          ? config_value->config.sdkVersion : icon_max_sdk;
+    }
+    if (icon_max_sdk < SDK_O) {
+      // Adaptive icons must be versioned with v26 qualifiers, so this is not an adaptive icon.
+      return;
+    }
+
+    // Find the roundIcon resource defined within the application.
+    auto round_icon_reference = ValueCast<Reference>(round_icon->compiled_value.get());
+    if (!round_icon_reference || !round_icon_reference->name) {
+      return;
+    }
+    package = table->FindPackageById(round_icon_reference->id.value().package_id());
+    if (!package) {
+      return;
+    }
+    type = package->FindType(round_icon_reference->name.value().type);
+    if (!type) {
+      return;
+    }
+    auto round_icon_entry = type->FindEntry(round_icon_reference->name.value().entry);
+    if (!round_icon_entry) {
+      return;
+    }
+
+    int round_icon_max_sdk = 0;
+    for (auto& config_value : round_icon_entry->values) {
+      round_icon_max_sdk = (round_icon_max_sdk < config_value->config.sdkVersion)
+                     ? config_value->config.sdkVersion : round_icon_max_sdk;
+    }
+    if (round_icon_max_sdk >= SDK_O) {
+      // The developer explicitly used a v26 compatible drawable as the roundIcon, meaning we should
+      // not generate an alias to the icon drawable.
+      return;
+    }
+
+    // Add an equivalent v26 entry to the roundIcon for each v26 variant of the regular icon.
+    for (auto& config_value : icon_entry->values) {
+      if (config_value->config.sdkVersion < SDK_O) {
+        continue;
+      }
+
+      context_->GetDiagnostics()->Note(DiagMessage() << "generating "
+                                                     << round_icon_reference->name.value()
+                                                     << " with config \"" << config_value->config
+                                                     << "\" for round icon compatibility");
+
+      auto value = icon_reference->Clone(&table->string_pool);
+      auto round_config_value = round_icon_entry->FindOrCreateValue(
+          config_value->config, config_value->product);
+      round_config_value->value.reset(value);
+    }
+  }
+
   // Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable
   // to the IArchiveWriter.
   bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, xml::XmlResource* manifest,
@@ -1578,6 +1665,14 @@
       return false;
     }
 
+    // When a developer specifies an adaptive application icon, and a non-adaptive round application
+    // icon, create an alias from the round icon to the regular icon for v26 APIs and up. We do this
+    // because certain devices prefer android:roundIcon over android:icon regardless of the API
+    // levels of the drawables set for either. This auto-aliasing behaviour allows an app to prefer
+    // the android:roundIcon on API 25 devices, and prefer the adaptive icon on API 26 devices.
+    // See (b/34829129)
+    AliasAdaptiveIcon(manifest, table);
+
     ResourceFileFlattenerOptions file_flattener_options;
     file_flattener_options.keep_raw_values = keep_raw_values;
     file_flattener_options.do_not_compress_anything = options_.do_not_compress_anything;
@@ -1592,7 +1687,6 @@
     file_flattener_options.output_format = options_.output_format;
 
     ResourceFileFlattener file_flattener(file_flattener_options, context_, keep_set);
-
     if (!file_flattener.Flatten(table, writer)) {
       context_->GetDiagnostics()->Error(DiagMessage() << "failed linking file resources");
       return false;