Persist kInvalidCredentials state

External code may want to do some additional processing when device
was unregistered from the cloud side, e.g. factory reset of the device.

Existing code switches from kInvalidCredentials to kUnregistered after
reboot. This makes processing kInvalidCredentials unreliable.

Persistence of kInvalidCredentials implemented as special case when
cloud_id is not empty but credentials are missing.

BUG:25342842

Change-Id: I80d4ce8157c70e132a55fd752a9006064cf70b57
Reviewed-on: https://weave-review.googlesource.com/1580
Reviewed-by: Vitaly Buka <vitalybuka@google.com>
diff --git a/include/weave/device.h b/include/weave/device.h
index e761e0c..19012b5 100644
--- a/include/weave/device.h
+++ b/include/weave/device.h
@@ -24,7 +24,7 @@
 
 // States of Gcd connection.
 enum class GcdState {
-  kUnconfigured,        // We have no credentials.
+  kUnconfigured,        // Device was not registered.
   kConnecting,          // We have credentials but not yet connected.
   kConnected,           // We're registered and connected to the cloud.
   kInvalidCredentials,  // Our registration has been revoked.
diff --git a/src/device_registration_info.cc b/src/device_registration_info.cc
index 8b8e752..751e530 100644
--- a/src/device_registration_info.cc
+++ b/src/device_registration_info.cc
@@ -262,6 +262,11 @@
   cloud_backoff_entry_.reset(new BackoffEntry{cloud_backoff_policy_.get()});
   oauth2_backoff_entry_.reset(new BackoffEntry{cloud_backoff_policy_.get()});
 
+  bool revoked =
+      !GetSettings().cloud_id.empty() && !HaveRegistrationCredentials();
+  gcd_state_ =
+      revoked ? GcdState::kInvalidCredentials : GcdState::kUnconfigured;
+
   command_manager_->AddCommandDefChanged(
       base::Bind(&DeviceRegistrationInfo::OnCommandDefsChanged,
                  weak_factory_.GetWeakPtr()));
@@ -511,9 +516,8 @@
 void DeviceRegistrationInfo::GetDeviceInfo(
     const CloudRequestDoneCallback& callback) {
   ErrorPtr error;
-  if (!VerifyRegistrationCredentials(&error)) {
+  if (!VerifyRegistrationCredentials(&error))
     return callback.Run({}, std::move(error));
-  }
   DoCloudRequest(HttpClient::Method::kGet, GetDeviceURL(), nullptr, callback);
 }
 
@@ -675,9 +679,8 @@
   // TODO(antonm): Add support for device removal.
 
   ErrorPtr error;
-  if (!VerifyRegistrationCredentials(&error)) {
+  if (!VerifyRegistrationCredentials(&error))
     return data->callback.Run({}, std::move(error));
-  }
 
   if (cloud_backoff_entry_->ShouldRejectRequest()) {
     VLOG(1) << "Cloud request delayed for "
@@ -762,13 +765,13 @@
 
 void DeviceRegistrationInfo::CheckAccessTokenError(ErrorPtr error) {
   if (error && error->HasError(kErrorDomainOAuth2, "invalid_grant"))
-    MarkDeviceUnregistered();
+    RemoveCredentials();
 }
 
 void DeviceRegistrationInfo::ConnectToCloud(ErrorPtr error) {
   if (error) {
     if (error->HasError(kErrorDomainOAuth2, "invalid_grant"))
-      MarkDeviceUnregistered();
+      RemoveCredentials();
     return;
   }
 
@@ -1270,10 +1273,10 @@
                  << cloud_id << "'";
     return;
   }
-  MarkDeviceUnregistered();
+  RemoveCredentials();
 }
 
-void DeviceRegistrationInfo::MarkDeviceUnregistered() {
+void DeviceRegistrationInfo::RemoveCredentials() {
   if (!HaveRegistrationCredentials())
     return;
 
@@ -1281,7 +1284,7 @@
 
   LOG(INFO) << "Device is unregistered from the cloud. Deleting credentials";
   Config::Transaction change{config_.get()};
-  change.set_cloud_id("");
+  // Keep cloud_id to switch to detect kInvalidCredentials after restart.
   change.set_robot_account("");
   change.set_refresh_token("");
   change.Commit();
diff --git a/src/device_registration_info.h b/src/device_registration_info.h
index 9d9d21f..1399b37 100644
--- a/src/device_registration_info.h
+++ b/src/device_registration_info.h
@@ -110,12 +110,6 @@
   // Starts GCD device if credentials available.
   void Start();
 
-  // Checks whether we have credentials generated during registration.
-  bool HaveRegistrationCredentials() const;
-  // Calls HaveRegistrationCredentials() and logs an error if no credentials
-  // are available.
-  bool VerifyRegistrationCredentials(ErrorPtr* error) const;
-
   // Updates a command (override from CloudCommandUpdateInterface).
   void UpdateCommand(const std::string& command_id,
                      const base::DictionaryValue& command_patch,
@@ -134,6 +128,12 @@
     return weak_factory_.GetWeakPtr();
   }
 
+  // Checks whether we have credentials generated during registration.
+  bool HaveRegistrationCredentials() const;
+  // Calls HaveRegistrationCredentials() and logs an error if no credentials
+  // are available.
+  bool VerifyRegistrationCredentials(ErrorPtr* error) const;
+
   // Cause DeviceRegistrationInfo to attempt to connect to cloud server on
   // its own later.
   void ScheduleCloudConnection(const base::TimeDelta& delay);
@@ -266,7 +266,7 @@
   void OnDeviceDeleted(const std::string& cloud_id) override;
 
   // Wipes out the device registration information and stops server connections.
-  void MarkDeviceUnregistered();
+  void RemoveCredentials();
 
   void RegisterDeviceError(const DoneCallback& callback, ErrorPtr error);
   void RegisterDeviceOnTicketSent(
diff --git a/src/device_registration_info_unittest.cc b/src/device_registration_info_unittest.cc
index 8174b90..df4a438 100644
--- a/src/device_registration_info_unittest.cc
+++ b/src/device_registration_info_unittest.cc
@@ -186,6 +186,10 @@
 
   GcdState GetGcdState() const { return dev_reg_->GetGcdState(); }
 
+  bool HaveRegistrationCredentials() const {
+    return dev_reg_->HaveRegistrationCredentials();
+  }
+
   provider::test::FakeTaskRunner task_runner_;
   provider::test::MockConfigStore config_store_;
   StrictMock<MockHttpClient> http_client_;
@@ -228,7 +232,7 @@
 }
 
 TEST_F(DeviceRegistrationInfoTest, HaveRegistrationCredentials) {
-  EXPECT_FALSE(dev_reg_->HaveRegistrationCredentials());
+  EXPECT_FALSE(HaveRegistrationCredentials());
   ReloadSettings();
 
   EXPECT_CALL(
@@ -253,7 +257,7 @@
       })));
 
   EXPECT_TRUE(RefreshAccessToken(nullptr));
-  EXPECT_TRUE(dev_reg_->HaveRegistrationCredentials());
+  EXPECT_TRUE(HaveRegistrationCredentials());
 }
 
 TEST_F(DeviceRegistrationInfoTest, CheckAuthenticationFailure) {
@@ -312,6 +316,7 @@
   EXPECT_FALSE(RefreshAccessToken(&error));
   EXPECT_TRUE(error->HasError(kErrorDomainOAuth2, "invalid_grant"));
   EXPECT_EQ(GcdState::kInvalidCredentials, GetGcdState());
+  EXPECT_EQ(test_data::kDeviceId, dev_reg_->GetSettings().cloud_id);
 }
 
 TEST_F(DeviceRegistrationInfoTest, GetDeviceInfo) {
diff --git a/src/privet/cloud_delegate.cc b/src/privet/cloud_delegate.cc
index 4c28141..fddc8e1 100644
--- a/src/privet/cloud_delegate.cc
+++ b/src/privet/cloud_delegate.cc
@@ -221,7 +221,8 @@
   void OnConfigChanged(const Settings&) { NotifyOnDeviceInfoChanged(); }
 
   void OnRegistrationChanged(GcdState status) {
-    if (status == GcdState::kUnconfigured) {
+    if (status == GcdState::kUnconfigured ||
+        status == GcdState::kInvalidCredentials) {
       connection_state_ = ConnectionState{ConnectionState::kUnconfigured};
     } else if (status == GcdState::kConnecting) {
       // TODO(vitalybuka): Find conditions for kOffline.