update_engine: Allow deprecation of DLC(s)

Currently, platform updates would block if any supported DLC(s) are
installed on the device and later CrOS version deprecates any of the
installed DLC(s).

[INFO:omaha_request_action.cc(501)] Request: <?xml version="1.0" encoding="UTF-8"?>
<request requestid="b59a48ec-6364-4a11-9400-860a35c1d85e" sessionid="" protocol="3.0" updater="ChromeOSUpdateEngine" updaterversion="0.1.0.0" installsource="scheduler" ismachine="1">
    <os version="Indy" platform="Chrome OS" sp="service_pack"></os>
    <app appid="test-app-id" version="0.1.0.0" track="" from_track="unittest" board="x86-generic" hardware_class="OEM MODEL 09235 7471" delta_okay="true" installdate="14" lang="en-US" fw_version="ChromeOSFirmware.1.0" ec_version="0X0A1" >
        <ping active="1" a="-1" r="-1"></ping>
        <updatecheck></updatecheck>
        <event eventtype="54" eventresult="1" previousversion="0.0.0.0"></event>
    </app>
    <app appid="test-app-id_dlc-id-1" version="0.1.0.0" track="" from_track="unittest" board="x86-generic" hardware_class="OEM MODEL 09235 7471" delta_okay="true" installdate="14" >
        <updatecheck></updatecheck>
    </app>
    <app appid="test-app-id_dlc-id-2" version="0.1.0.0" track="" from_track="unittest" board="x86-generic" hardware_class="OEM MODEL 09235 7471" delta_okay="true" installdate="14" >
        <updatecheck></updatecheck>
    </app>
</request>

[INFO:omaha_request_action.cc(903)] Omaha request response: <?xml version="1.0" encoding="UTF-8"?><response protocol="3.0"><daystart elapsed_seconds="100" elapsed_days="42"/><app appid="test-app-id"  status="ok"><ping status="ok"/><updatecheck status="ok" _firmware_version_0="" _kernel_version_0=""><urls><url codebase="http://code/base/"/></urls><manifest version="1.2.3.4"><packages><package hash="not-used" name="file.signed" size="123" hash_sha256="4841534831323334"/></packages><actions><action event="postinstall" MetadataSize="11" MoreInfo="http://more/info" Prompt="true" IsDeltaPayload="true" MaxDaysToScatter="7" sha256="not-used" /></actions></manifest></updatecheck></app><app appid="test-app-id_dlc-id-1" status="ok"><updatecheck status="ok"><urls><url codebase="http://code/base/"/></urls><manifest version="1.2.3.4"><packages><package name="package3" size="333" hash_sha256="hash3"/></packages><actions><action event="install" run=".signed"/><action event="postinstall" MetadataSize="33"/></actions></manifest></updatecheck></app><app appid="test-app-id_dlc-id-2"><updatecheck status="noupdate"/></app></response>
...
[INFO:omaha_request_action.cc(706)] Found 3 <app>.
[INFO:omaha_request_action.cc(811)] Update for <app> test-app-id
[INFO:omaha_request_action.cc(811)] Update for <app> test-app-id_dlc-id-1
[INFO:omaha_request_action.cc(794)] No update for <app> test-app-id_dlc-id-2 but update continuing since a DLC.
...

BUG=chromium:1050777
TEST=# update_engine unittest

Change-Id: I43640f3463aa74c67465a2027bc530794eb73210
Reviewed-on: https://chromium-review.googlesource.com/c/aosp/platform/system/update_engine/+/2053077
Tested-by: Jae Hoon Kim <kimjae@chromium.org>
Commit-Queue: Amin Hassani <ahassani@chromium.org>
Reviewed-by: Amin Hassani <ahassani@chromium.org>
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
index b6b4356..58e7f47 100644
--- a/omaha_request_action.cc
+++ b/omaha_request_action.cc
@@ -788,6 +788,13 @@
   for (const auto& app : parser_data->apps) {
     const string& status = app.updatecheck_status;
     if (status == kValNoUpdate) {
+      // If the app is a DLC, allow status "noupdate" to support DLC
+      // deprecations.
+      if (params_->IsDlcAppId(app.id)) {
+        LOG(INFO) << "No update for <app> " << app.id
+                  << " but update continuing since a DLC.";
+        continue;
+      }
       // Don't update if any app has status="noupdate".
       LOG(INFO) << "No update for <app> " << app.id;
       output_object->update_exists = false;
diff --git a/omaha_request_action_unittest.cc b/omaha_request_action_unittest.cc
index d1cb4ed..2528f7b 100644
--- a/omaha_request_action_unittest.cc
+++ b/omaha_request_action_unittest.cc
@@ -86,6 +86,8 @@
 const char kTestAppId[] = "test-app-id";
 const char kTestAppId2[] = "test-app2-id";
 const char kTestAppIdSkipUpdatecheck[] = "test-app-id-skip-updatecheck";
+const char kDlcId1[] = "dlc-id-1";
+const char kDlcId2[] = "dlc-id-2";
 
 // This is a helper struct to allow unit tests build an update response with the
 // values they care about.
@@ -131,6 +133,8 @@
   }
 
   string GetUpdateResponse() const {
+    chromeos_update_engine::OmahaRequestParams request_params{nullptr};
+    request_params.set_app_id(app_id);
     return "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
            "protocol=\"3.0\">"
            "<daystart elapsed_seconds=\"100\"" +
@@ -196,6 +200,21 @@
            (multi_app_skip_updatecheck
                 ? "<app appid=\"" + app_id_skip_updatecheck + "\"></app>"
                 : "") +
+           (dlc_app_update
+                ? "<app appid=\"" + request_params.GetDlcAppId(kDlcId1) +
+                      "\" status=\"ok\">"
+                      "<updatecheck status=\"ok\"><urls><url codebase=\"" +
+                      codebase + "\"/></urls><manifest version=\"" + version +
+                      "\"><packages><package name=\"package3\" size=\"333\" "
+                      "hash_sha256=\"hash3\"/></packages><actions>"
+                      "<action event=\"install\" run=\".signed\"/>"
+                      "<action event=\"postinstall\" MetadataSize=\"33\"/>"
+                      "</actions></manifest></updatecheck></app>"
+                : "") +
+           (dlc_app_no_update
+                ? "<app appid=\"" + request_params.GetDlcAppId(kDlcId2) +
+                      "\"><updatecheck status=\"noupdate\"/></app>"
+                : "") +
            "</response>";
   }
 
@@ -244,6 +263,10 @@
   bool multi_app_skip_updatecheck = false;
   // Whether to include more than one package in an app.
   bool multi_package = false;
+  // Whether to include a DLC app with updatecheck tag.
+  bool dlc_app_update = false;
+  // Whether to include a DLC app with no updatecheck tag.
+  bool dlc_app_no_update = false;
 
   // Whether the payload is a rollback.
   bool rollback = false;
@@ -2688,6 +2711,7 @@
     pos++;
   }
   EXPECT_EQ(request_params_.dlc_apps_params().size(), updatecheck_count);
+  EXPECT_TRUE(response.update_exists);
 }
 
 TEST_F(OmahaRequestActionTest, InstallMissingPlatformVersionTest) {
@@ -2706,6 +2730,38 @@
   EXPECT_EQ(fake_update_response_.current_version, response.version);
 }
 
+TEST_F(OmahaRequestActionTest, UpdateWithDlcTest) {
+  request_params_.set_dlc_apps_params(
+      {{request_params_.GetDlcAppId(kDlcId1), {.name = kDlcId1}}});
+  fake_update_response_.dlc_app_update = true;
+  tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+  ASSERT_TRUE(TestUpdateCheck());
+
+  EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, UpdateWithDeprecatedDlcTest) {
+  request_params_.set_dlc_apps_params(
+      {{request_params_.GetDlcAppId(kDlcId2), {.name = kDlcId2}}});
+  fake_update_response_.dlc_app_no_update = true;
+  tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+  ASSERT_TRUE(TestUpdateCheck());
+
+  EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, UpdateWithDlcAndDeprecatedDlcTest) {
+  request_params_.set_dlc_apps_params(
+      {{request_params_.GetDlcAppId(kDlcId1), {.name = kDlcId1}},
+       {request_params_.GetDlcAppId(kDlcId2), {.name = kDlcId2}}});
+  fake_update_response_.dlc_app_update = true;
+  fake_update_response_.dlc_app_no_update = true;
+  tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
+  ASSERT_TRUE(TestUpdateCheck());
+
+  EXPECT_TRUE(response.update_exists);
+}
+
 TEST_F(OmahaRequestActionTest, PastRollbackVersionsNoEntries) {
   fake_update_response_.rollback = true;
   fake_update_response_.rollback_allowed_milestones = 4;
diff --git a/omaha_request_params.cc b/omaha_request_params.cc
index e6d96a4..1cfbc9c 100644
--- a/omaha_request_params.cc
+++ b/omaha_request_params.cc
@@ -249,10 +249,14 @@
                                                : image_props_.product_id;
 }
 
-string OmahaRequestParams::GetDlcAppId(std::string dlc_id) const {
+string OmahaRequestParams::GetDlcAppId(const std::string& dlc_id) const {
   // Create APP ID according to |dlc_id| (sticking the current AppID to the
   // DLC module ID with an underscode).
   return GetAppId() + "_" + dlc_id;
 }
 
+bool OmahaRequestParams::IsDlcAppId(const std::string& app_id) const {
+  return dlc_apps_params().find(app_id) != dlc_apps_params().end();
+}
+
 }  // namespace chromeos_update_engine
diff --git a/omaha_request_params.h b/omaha_request_params.h
index b984002..14f3eaf 100644
--- a/omaha_request_params.h
+++ b/omaha_request_params.h
@@ -225,9 +225,12 @@
   // download channel.
   virtual std::string GetAppId() const;
 
-  // Returns the DLC app ID corresponding to the current value of the
-  // download channel.
-  virtual std::string GetDlcAppId(std::string dlc_id) const;
+  // Returns the DLC app ID.
+  virtual std::string GetDlcAppId(const std::string& dlc_id) const;
+
+  // Returns true if the App ID is a DLC App ID that is currently part of the
+  // request parameters.
+  virtual bool IsDlcAppId(const std::string& app_id) const;
 
   // Suggested defaults
   static const char kOsVersion[];
@@ -404,7 +407,7 @@
   // The metadata/prefs root path for DLCs.
   std::string dlc_prefs_root_;
 
-  // A list of DLC modules to install.
+  // A list of DLC modules to install. A mapping from DLC App ID to |AppParams|.
   std::map<std::string, AppParams> dlc_apps_params_;
 
   // This variable defines whether the payload is being installed in the current