cutoff: Not modify the charge mode when ac is disconnected

We can't modify the charge mode when ac is disconnected.
If the flow required ac, we should call wait_ac_connected
explicitly.

BUG=b:384838432
TEST=CQ

Change-Id: Icfabd2a7bc9b959c6af630e1516da0cbf124b3ff
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/factory_installer/+/6156203
Tested-by: Chih-Yao Chuang <jasonchuang@google.com>
Auto-Submit: Chih-Yao Chuang <jasonchuang@google.com>
Commit-Queue: Chih-Yao Chuang <jasonchuang@google.com>
Commit-Queue: Yu-An Wang <wyuang@google.com>
Reviewed-by: Yu-An Wang <wyuang@google.com>
Code-Coverage: Zoss <zoss-cl-coverage@prod.google.com>
diff --git a/rust/src/cutoff/mod.rs b/rust/src/cutoff/mod.rs
index 2993d89..ca187b2 100644
--- a/rust/src/cutoff/mod.rs
+++ b/rust/src/cutoff/mod.rs
@@ -176,9 +176,7 @@
 }
 
 fn discharge_battery(config: &CutoffConfig, context: &mut dyn Context) -> Result<()> {
-    if ectool_utils::charge_control(ChargeControl::Discharge, context).is_err() {
-        eprintln!("Not set to discharge mode since AC is disconnected.");
-    }
+    ectool_utils::charge_control(ChargeControl::Discharge, context)?;
 
     // Use stressapptest to discharge battery faster.
     // It may crash the system if it use too much memory on Factory Shim.
@@ -356,7 +354,8 @@
             &mut context,
         );
         mock_battery_info(60, true, &mut context); // No need to charge or discharge
-        context.set_command_stdout("ectool", "".to_string());
+        mock_battery_info(60, true, &mut context); // For charge control
+        context.set_command_stdout("ectool", "idle".to_string());
         mock_battery_info(60, true, &mut context); // Wait ac disconnected
         context.set_command_stdout(
             context
@@ -365,7 +364,8 @@
             "remove ac".to_string(),
         );
         mock_battery_info(60, false, &mut context); // ac disconnected
-        context.set_command_stdout("ectool", "".to_string());
+        mock_battery_info(60, true, &mut context); // For charge control
+        context.set_command_stdout("ectool", "normal".to_string());
         context.set_command_stdout(
             context
                 .root_dir()
@@ -387,7 +387,8 @@
     #[test]
     fn test_cutoff_battery_reboot() {
         let mut context = ContextImpl::new();
-        context.set_command_stdout("ectool", "".to_string());
+        mock_battery_info(60, true, &mut context); // For charge control
+        context.set_command_stdout("ectool", "normal".to_string());
         context.set_command_stdout(
             context
                 .root_dir()
@@ -409,7 +410,8 @@
     #[test]
     fn test_cutoff_battery_battery_cutoff() {
         let mut context = ContextImpl::new();
-        context.set_command_stdout("ectool", "".to_string());
+        mock_battery_info(60, true, &mut context); // For charge control
+        context.set_command_stdout("ectool", "normal".to_string());
         context.set_command_stdout("crossystem", "".to_string());
         context.set_command_stdout(
             context
@@ -432,7 +434,8 @@
     #[test]
     fn test_cutoff_battery_ec_hibernate() {
         let mut context = ContextImpl::new();
-        context.set_command_stdout("ectool", "".to_string());
+        mock_battery_info(60, true, &mut context); // For charge control
+        context.set_command_stdout("ectool", "normal".to_string());
         context.set_command_stdout("ectool", "".to_string());
         context.set_command_stdout(
             context
@@ -455,7 +458,8 @@
     #[test]
     fn test_cutoff_battery_shutdown() {
         let mut context = ContextImpl::new();
-        context.set_command_stdout("ectool", "".to_string());
+        mock_battery_info(60, true, &mut context); // For charge control
+        context.set_command_stdout("ectool", "normal".to_string());
         context.set_command_stdout("crossystem", "recovery".to_string());
         context.set_command_stdout("ectool", "".to_string());
         context.set_command_stdout(
@@ -479,7 +483,8 @@
     #[test]
     fn test_cutoff_battery_unknown_method() {
         let mut context = ContextImpl::new();
-        context.set_command_stdout("ectool", "".to_string());
+        mock_battery_info(60, true, &mut context); // For charge control
+        context.set_command_stdout("ectool", "normal".to_string());
         context.set_command_stdout(
             context
                 .root_dir()
@@ -503,7 +508,8 @@
     #[test]
     fn test_cutoff_battery_config_missing_state() {
         let mut context = ContextImpl::new();
-        context.set_command_stdout("ectool", "".to_string());
+        mock_battery_info(60, true, &mut context); // For charge control
+        context.set_command_stdout("ectool", "normal".to_string());
         context.set_command_stdout(
             context
                 .root_dir()
@@ -528,7 +534,8 @@
     #[test]
     fn test_cutoff_battery_config_missing_method() {
         let mut context = ContextImpl::new();
-        context.set_command_stdout("ectool", "".to_string());
+        mock_battery_info(60, true, &mut context); // For charge control
+        context.set_command_stdout("ectool", "normal".to_string());
         context.set_command_stdout(
             context
                 .root_dir()
@@ -634,6 +641,7 @@
             "connect_ac".to_string(),
         );
         mock_battery_info(60, true, &mut context); // Ac connected
+        mock_battery_info(60, true, &mut context); // For charge control
         context.set_command_stdout("ectool", "normal".to_string());
         context.set_command_stdout(
             context
@@ -667,6 +675,7 @@
                 .join("usr/share/cutoff/display_wipe_message.sh"),
             "remove_ac".to_string(),
         );
+        mock_battery_info(60, true, &mut context); // For charge control
         context.set_command_stdout("ectool", "discharge".to_string());
         context.set_command_stdout("stressapptest", "".to_string());
         context.set_command_stdout(
@@ -683,40 +692,6 @@
     }
 
     #[test]
-    fn test_check_battery_discharge_ac_disconnected() {
-        let mut context = ContextImpl::new();
-        context.set_command_stdout("modprobe", "".to_string());
-        create_cutoff_files(
-            json!({
-                "CUTOFF_AC_STATE": "connect_ac",
-                "CUTOFF_METHOD": "shutdown",
-                "CUTOFF_BATTERY_MAX_PERCENTAGE": 70,
-            }),
-            &mut context,
-        );
-        mock_battery_info(80, true, &mut context); // Need to discharge
-        context.set_command_stdout(
-            context
-                .root_dir()
-                .join("usr/share/cutoff/display_wipe_message.sh"),
-            "remove_ac".to_string(),
-        );
-        context.set_command_stderr("ectool", "discharge fail".to_string());
-        context.set_command_stdout("stressapptest", "".to_string());
-        context.set_command_stdout(
-            context
-                .root_dir()
-                .join("usr/share/cutoff/display_wipe_message.sh"),
-            "discharging".to_string(),
-        );
-        mock_battery_info(80, true, &mut context); // Wait discharge
-        mock_battery_info(70, true, &mut context); // Discharge completed
-
-        let result = cutoff::check_battery(&mut context);
-        assert!(result.is_ok());
-    }
-
-    #[test]
     fn test_display_qrcode() {
         let mut context = ContextImpl::new();
         context.set_command_stdout("modprobe", "".to_string());
@@ -800,7 +775,8 @@
     #[test]
     fn test_cutoff_use_toolkit_path() {
         let mut context = ContextImpl::new();
-        context.set_command_stdout("ectool", "".to_string());
+        mock_battery_info(60, true, &mut context); // For charge control
+        context.set_command_stdout("ectool", "normal".to_string());
         context.set_command_stdout(
             context
                 .root_dir()
@@ -823,7 +799,8 @@
     #[test]
     fn test_cutoff_use_private_overlay_path() {
         let mut context = ContextImpl::new();
-        context.set_command_stdout("ectool", "".to_string());
+        mock_battery_info(60, true, &mut context); // For charge control
+        context.set_command_stdout("ectool", "normal".to_string());
         context.set_command_stdout(
             context
                 .root_dir()
@@ -848,7 +825,8 @@
     #[test]
     fn test_cutoff_config_not_exist() {
         let mut context = ContextImpl::new();
-        context.set_command_stdout("ectool", "".to_string());
+        mock_battery_info(60, true, &mut context); // For charge control
+        context.set_command_stdout("ectool", "normal".to_string());
         context.set_command_stdout(
             context
                 .root_dir()
@@ -869,7 +847,8 @@
     #[test]
     fn test_display_script_not_exist() {
         let mut context = ContextImpl::new();
-        context.set_command_stdout("ectool", "".to_string());
+        mock_battery_info(60, true, &mut context); // For charge control
+        context.set_command_stdout("ectool", "normal".to_string());
 
         let result = cutoff::cutoff_battery(&mut context);
 
diff --git a/rust/src/factory_installer/mod.rs b/rust/src/factory_installer/mod.rs
index b544a0e..0525ee7 100644
--- a/rust/src/factory_installer/mod.rs
+++ b/rust/src/factory_installer/mod.rs
@@ -319,7 +319,12 @@
     }
 
     fn mock_cutoff_battery(context: &mut ContextImpl) {
-        context.set_command_stdout("ectool", "".to_string());
+        context.set_command_stdout(
+            "ectool",
+            "Design capacity: 100 mAh Remaining capacity 100 mAh Present voltage 10 mV AC_PRESENT"
+                .to_string(),
+        ); // For charge control
+        context.set_command_stdout("ectool", "normal".to_string());
         context.set_command_stdout(
             context
                 .root_dir()
diff --git a/rust/src/utils/ectool_utils.rs b/rust/src/utils/ectool_utils.rs
index af58c80..af98eab 100644
--- a/rust/src/utils/ectool_utils.rs
+++ b/rust/src/utils/ectool_utils.rs
@@ -99,6 +99,11 @@
 }
 
 pub fn charge_control(mode: ChargeControl, context: &mut dyn Context) -> Result<()> {
+    let info = get_battery_info(context)?;
+    if !info.is_ac_connected {
+        eprintln!("Can't modify the charge mode since AC is disconnected.");
+        return Ok(());
+    }
     let output = context
         .command("ectool")
         .args(["chargecontrol", &mode.to_string()])
@@ -184,6 +189,11 @@
     #[test]
     fn test_charge_control_success() {
         let mut context = ContextImpl::new();
+        context.set_command_stdout(
+            "ectool",
+            "Design capacity: 100 mAh Remaining capacity 100 mAh Present voltage 10 mV AC_PRESENT"
+                .to_string(),
+        );
         context.set_command_stdout("ectool", "".to_string());
         let result = ectool_utils::charge_control(ChargeControl::Idle, &mut context);
         assert!(result.is_ok());
@@ -192,6 +202,11 @@
     #[test]
     fn test_charge_control_fail() {
         let mut context = ContextImpl::new();
+        context.set_command_stdout(
+            "ectool",
+            "Design capacity: 100 mAh Remaining capacity 100 mAh Present voltage 10 mV AC_PRESENT"
+                .to_string(),
+        );
         context.set_command_stderr("ectool", "stderr".to_string());
         let result = ectool_utils::charge_control(ChargeControl::Idle, &mut context);
         let err = result.unwrap_err();
@@ -200,6 +215,17 @@
     }
 
     #[test]
+    fn test_charge_control_ac_disconnected_skip() {
+        let mut context = ContextImpl::new();
+        context.set_command_stdout(
+            "ectool",
+            "Design capacity: 100 mAh Remaining capacity 100 mAh Present voltage 10 mV".to_string(),
+        );
+        let result = ectool_utils::charge_control(ChargeControl::Idle, &mut context);
+        assert!(result.is_ok());
+    }
+
+    #[test]
     fn test_reboot_ec_success() {
         let mut context = ContextImpl::new();
         context.set_command_stdout("ectool", "".to_string());