update_engine: Powerwash based on version

Powerwash after an update is currently enforced based on the channel: If
the new channel is more stable, powerwash happens. This may cause
unnecessary powerwash if the currently installed Chrome OS version is
old enough so that a normal update (e.g. 12817.68.0 -> 12817.76.0) can
take place.

Additionally decide whether a powerwash should take place based on the
new and old version number. Only powerwash if the new version number is
older than old version number.

BUG=chromium:1070563
TEST=FEATURES=test emerge-amd64-generic update_engine
channel change and update on test device

Change-Id: Ib211cf87711bde9f9c912395548124dcbb1194bb
Reviewed-on: https://chromium-review.googlesource.com/c/aosp/platform/system/update_engine/+/2162986
Tested-by: Miriam Polzer <mpolzer@google.com>
Commit-Queue: Miriam Polzer <mpolzer@google.com>
Reviewed-by: Amin Hassani <ahassani@chromium.org>
Reviewed-by: Jae Hoon Kim <kimjae@chromium.org>
diff --git a/omaha_request_params.h b/omaha_request_params.h
index d29ce70..3452965 100644
--- a/omaha_request_params.h
+++ b/omaha_request_params.h
@@ -266,8 +266,10 @@
   // or Init is called again.
   virtual void UpdateDownloadChannel();
 
-  // Returns whether we should powerwash for this update.
-  virtual bool ShouldPowerwash() const;
+  // Returns whether we should powerwash for this update. Note that this is
+  // just an indication, the final decision to powerwash or not is made in the
+  // response handler.
+  bool ShouldPowerwash() const;
 
   // Check if the provided update URL is official, meaning either the default
   // autoupdate server or the autoupdate autotest server.
diff --git a/omaha_response_handler_action.cc b/omaha_response_handler_action.cc
index c25b76f..915e839 100644
--- a/omaha_response_handler_action.cc
+++ b/omaha_response_handler_action.cc
@@ -21,6 +21,7 @@
 
 #include <base/logging.h>
 #include <base/strings/string_number_conversions.h>
+#include <base/version.h>
 #include <policy/device_policy.h>
 
 #include "update_engine/common/constants.h"
@@ -183,8 +184,26 @@
         params->rollback_data_save_requested();
   }
 
-  if (response.powerwash_required || params->ShouldPowerwash())
+  // Powerwash if either the response requires it or the parameters indicated
+  // powerwash and we are downgrading the version.
+  if (response.powerwash_required) {
     install_plan_.powerwash_required = true;
+  } else if (params->ShouldPowerwash()) {
+    base::Version new_version(response.version);
+    base::Version current_version(params->app_version());
+
+    if (!new_version.IsValid()) {
+      LOG(WARNING) << "Not powerwashing,"
+                   << " the update's version number is unreadable."
+                   << " Update's version number: " << response.version;
+    } else if (!current_version.IsValid()) {
+      LOG(WARNING) << "Not powerwashing,"
+                   << " the current version number is unreadable."
+                   << " Current version number: " << params->app_version();
+    } else if (new_version < current_version) {
+      install_plan_.powerwash_required = true;
+    }
+  }
 
   TEST_AND_RETURN(HasOutputPipe());
   if (HasOutputPipe())
diff --git a/omaha_response_handler_action_unittest.cc b/omaha_response_handler_action_unittest.cc
index 0ebf848..04cfa73 100644
--- a/omaha_response_handler_action_unittest.cc
+++ b/omaha_response_handler_action_unittest.cc
@@ -430,10 +430,11 @@
   EXPECT_EQ(in.version, install_plan.version);
 }
 
-TEST_F(OmahaResponseHandlerActionTest, ChangeToMoreStableChannelTest) {
+TEST_F(OmahaResponseHandlerActionTest,
+       ChangeToMoreStableVersionAndChannelTest) {
   OmahaResponse in;
   in.update_exists = true;
-  in.version = "a.b.c.d";
+  in.version = "1.0.0.0";
   in.packages.push_back({.payload_urls = {"https://MoreStableChannelTest"},
                          .size = 1,
                          .hash = kPayloadHashHex});
@@ -454,7 +455,7 @@
 #endif  // __ANDROID__
   EXPECT_TRUE(params.SetTargetChannel("stable-channel", true, nullptr));
   params.UpdateDownloadChannel();
-  EXPECT_TRUE(params.ShouldPowerwash());
+  params.set_app_version("2.0.0.0");
 
   fake_system_state_.set_request_params(&params);
   InstallPlan install_plan;
@@ -462,10 +463,79 @@
   EXPECT_TRUE(install_plan.powerwash_required);
 }
 
-TEST_F(OmahaResponseHandlerActionTest, ChangeToLessStableChannelTest) {
+TEST_F(OmahaResponseHandlerActionTest,
+       ChangeToMoreStableVersionAndChannelPowerwashNotAllowedTest) {
   OmahaResponse in;
   in.update_exists = true;
-  in.version = "a.b.c.d";
+  in.version = "1.0.0.0";
+  in.packages.push_back({.payload_urls = {"https://MoreStableChannelTest"},
+                         .size = 1,
+                         .hash = kPayloadHashHex});
+  in.more_info_url = "http://more/info";
+
+  // Create a uniquely named test directory.
+  base::ScopedTempDir tempdir;
+  ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+
+  OmahaRequestParams params(&fake_system_state_);
+  fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
+  params.set_root(tempdir.GetPath().value());
+  params.set_current_channel("canary-channel");
+  // The |ImageProperties| in Android uses prefs to store
+  // |MutableImageProperties|.
+#ifdef __ANDROID__
+  EXPECT_CALL(*fake_system_state_.mock_prefs(), SetBoolean(_, true))
+      .WillOnce(Return(true));
+#endif  // __ANDROID__
+  EXPECT_TRUE(params.SetTargetChannel("stable-channel", false, nullptr));
+  params.UpdateDownloadChannel();
+  params.set_app_version("2.0.0.0");
+
+  fake_system_state_.set_request_params(&params);
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
+  EXPECT_FALSE(install_plan.powerwash_required);
+}
+
+TEST_F(OmahaResponseHandlerActionTest,
+       ChangeToMoreStableChannelButNewerVersionTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.version = "12345.96.0.0";
+  in.packages.push_back({.payload_urls = {"https://ChannelDownVersionUp"},
+                         .size = 1,
+                         .hash = kPayloadHashHex});
+  in.more_info_url = "http://more/info";
+
+  // Create a uniquely named test directory.
+  base::ScopedTempDir tempdir;
+  ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+
+  OmahaRequestParams params(&fake_system_state_);
+  fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
+  params.set_root(tempdir.GetPath().value());
+  params.set_current_channel("beta-channel");
+  // The |ImageProperties| in Android uses prefs to store
+  // |MutableImageProperties|.
+#ifdef __ANDROID__
+  EXPECT_CALL(*fake_system_state_.mock_prefs(), SetBoolean(_, true))
+      .WillOnce(Return(true));
+#endif  // __ANDROID__
+  EXPECT_TRUE(params.SetTargetChannel("stable-channel", true, nullptr));
+  params.UpdateDownloadChannel();
+  params.set_app_version("12345.48.0.0");
+
+  fake_system_state_.set_request_params(&params);
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
+  EXPECT_FALSE(install_plan.powerwash_required);
+}
+
+TEST_F(OmahaResponseHandlerActionTest,
+       ChangeToLessStableVersionAndChannelTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.version = "2.0.0.0";
   in.packages.push_back({.payload_urls = {"https://LessStableChannelTest"},
                          .size = 15,
                          .hash = kPayloadHashHex});
@@ -486,7 +556,7 @@
 #endif  // __ANDROID__
   EXPECT_TRUE(params.SetTargetChannel("canary-channel", false, nullptr));
   params.UpdateDownloadChannel();
-  EXPECT_FALSE(params.ShouldPowerwash());
+  params.set_app_version("1.0.0.0");
 
   fake_system_state_.set_request_params(&params);
   InstallPlan install_plan;