[Battery Saver] Power Setting UI

This change is the initial (incomplete) implementation of the settings
UI for ChromeOS's battery saver feature.

Everything is behind a feature+flag, so regular users won't see the
incomplete (and not internationalized) UI. But this makes it available
for teamfood and fishfood.

Battery saver is a mode that trades reduced performance for increased
battery life.

The toggle should appear in settings when the feature is enabled and the
device has a battery.

Battery saver can not be enabled when on AC power, so the toggle should
be ghosted when charging.

Screenshot: https://screenshot.googleplex.com/hdAxcqGHLETwiLC

UI design: go/cros-bsm-mocks

BUG=b:278957245
TEST=Locally, reenable OSSettingsDevicePageTest and run it.

Change-Id: Icae4bd1aac80e6cf135aec2c9e842cb32859ef5c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4318204
Reviewed-by: Wes Okuhara <wesokuhara@google.com>
Commit-Queue: Charles William Dick <cwd@google.com>
Reviewed-by: Takashi Toyoshima <toyoshim@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1157352}
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index 6620233..6595c62f 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -4615,6 +4615,12 @@
   <message name="IDS_SETTINGS_BATTERY_STATUS_SHORT" desc="In Device Settings > Power, the battery status when the time left cannot be shown.">
     <ph name="percentage">$1<ex>56</ex></ph>%
   </message>
+  <message name="IDS_SETTINGS_POWER_BATTERY_SAVER_LABEL" desc="In Device Settings > Power, label for toggling battery saver mode" translateable="false">
+    Battery Saver
+  </message>
+  <message name="IDS_SETTINGS_POWER_BATTERY_SAVER_SUBTEXT" desc="In Device Settings > Power, description of the behavior of battery saver mode" translateable="false">
+    Enable Energy saver to extend battery life by turning off background functions such as[...]
+  </message>
 
   <!-- Reset Page (OS settings)-->
   <message name="IDS_SETTINGS_RESET_TITLE" desc="Title of a section of settings. This section describes settings for resetting the device.">
diff --git a/chrome/browser/extensions/api/settings_private/prefs_util.cc b/chrome/browser/extensions/api/settings_private/prefs_util.cc
index d0ef1a00..0e68454 100644
--- a/chrome/browser/extensions/api/settings_private/prefs_util.cc
+++ b/chrome/browser/extensions/api/settings_private/prefs_util.cc
@@ -928,6 +928,8 @@
       settings_api::PrefType::PREF_TYPE_LIST;
   (*s_allowlist)[ash::prefs::kPowerAdaptiveChargingEnabled] =
       settings_api::PrefType::PREF_TYPE_BOOLEAN;
+  (*s_allowlist)[ash::prefs::kPowerBatterySaver] =
+      settings_api::PrefType::PREF_TYPE_BOOLEAN;
   (*s_allowlist)[::prefs::kConsumerAutoUpdateToggle] =
       settings_api::PrefType::PREF_TYPE_BOOLEAN;
   (*s_allowlist)[::ash::prefs::kChargingSoundsEnabled] =
diff --git a/chrome/browser/resources/settings/chromeos/device_page/device_page.html b/chrome/browser/resources/settings/chromeos/device_page/device_page.html
index c15d741..f740fe2 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/device_page.html
+++ b/chrome/browser/resources/settings/chromeos/device_page/device_page.html
@@ -182,7 +182,7 @@
   <template is="dom-if" route-path="/power">
     <os-settings-subpage
         page-title="$i18n{powerTitle}">
-      <settings-power></settings-power>
+      <settings-power prefs="{{prefs}}"></settings-power>
     </os-settings-subpage>
   </template>
 </os-settings-animated-pages>
diff --git a/chrome/browser/resources/settings/chromeos/device_page/device_page_browser_proxy.ts b/chrome/browser/resources/settings/chromeos/device_page/device_page_browser_proxy.ts
index 137ab60..8dbc90b 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/device_page_browser_proxy.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/device_page_browser_proxy.ts
@@ -78,6 +78,7 @@
   hasLid: boolean;
   adaptiveCharging: boolean;
   adaptiveChargingManaged: boolean;
+  batterySaverFeatureEnabled: boolean;
 }
 
 /**
diff --git a/chrome/browser/resources/settings/chromeos/device_page/power.html b/chrome/browser/resources/settings/chromeos/device_page/power.html
index c254544..b645d825 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/power.html
+++ b/chrome/browser/resources/settings/chromeos/device_page/power.html
@@ -36,6 +36,16 @@
   </div>
 </div>
 
+<settings-toggle-button id="batterySaverToggle"
+    hidden$="[[batterySaverHidden_]]"
+    pref="{{prefs.power.cros_battery_saver_active}}"
+    label="$i18n{powerBatterySaverLabel}"
+    sub-label="$i18n{powerBatterySaverSubtext}"
+    learn-more-url="$i18n{powerBatterySaverLearnMoreUrl}"
+    disabled="[[batterySaverToggleDisabled_]]"
+    deep-link-focus-id$="[[Setting.kBatterySaver]]">
+</settings-toggle-button>
+
 <settings-toggle-button hidden$="[[!adaptiveChargingEnabled_]]"
   class$="[[getClassForRow_(batteryStatus_.present, 'adaptiveCharging')]]"
   id="adaptiveChargingToggle"
diff --git a/chrome/browser/resources/settings/chromeos/device_page/power.ts b/chrome/browser/resources/settings/chromeos/device_page/power.ts
index 5f341fc..f08010a 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/power.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/power.ts
@@ -15,6 +15,7 @@
 import '../settings_shared.css.js';
 
 import {SettingsToggleButtonElement} from '/shared/settings/controls/settings_toggle_button.js';
+import {PrefsMixin} from 'chrome://resources/cr_components/settings_prefs/prefs_mixin.js';
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {WebUiListenerMixin} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js';
 import {assertNotReached} from 'chrome://resources/js/assert_ts.js';
@@ -39,13 +40,14 @@
 interface SettingsPowerElement {
   $: {
     adaptiveChargingToggle: SettingsToggleButtonElement,
+    batterySaverToggle: SettingsToggleButtonElement,
     lidClosedToggle: SettingsToggleButtonElement,
     powerSource: HTMLSelectElement,
   };
 }
 
-const SettingsPowerElementBase = DeepLinkingMixin(
-    RouteObserverMixin(WebUiListenerMixin(I18nMixin(PolymerElement))));
+const SettingsPowerElementBase = DeepLinkingMixin(RouteObserverMixin(
+    PrefsMixin(WebUiListenerMixin(I18nMixin(PolymerElement)))));
 
 class SettingsPowerElement extends SettingsPowerElementBase {
   static get is() {
@@ -163,6 +165,20 @@
         },
       },
 
+      batterySaverFeatureEnabled_: Boolean,
+
+      batterySaverHidden_: {
+        type: Boolean,
+        computed:
+            'computeBatterySaverHidden_(batteryStatus_, batterySaverFeatureEnabled_)',
+      },
+
+      batterySaverToggleDisabled_: {
+        type: Boolean,
+        computed:
+            'computeBatterySaverToggleDisabled_(powerSources_, lowPowerCharger_)',
+      },
+
       /**
        * Used by DeepLinkingMixin to focus this page's deep links.
        */
@@ -174,6 +190,7 @@
           Setting.kSleepWhenLaptopLidClosed,
           Setting.kPowerIdleBehaviorWhileOnBattery,
           Setting.kAdaptiveCharging,
+          Setting.kBatterySaver,
         ]),
       },
 
@@ -195,6 +212,7 @@
   private lowPowerCharger_: boolean;
   private powerSources_: PowerSource[]|undefined;
   private selectedPowerSourceId_: string;
+  private batterySaverFeatureEnabled_: boolean;
 
   constructor() {
     super();
@@ -275,6 +293,24 @@
     return '';
   }
 
+  private computeBatterySaverHidden_(
+      batteryStatus: BatteryStatus|undefined,
+      featureEnabled: boolean): boolean {
+    if (batteryStatus === undefined) {
+      return true;
+    }
+    return !featureEnabled || !batteryStatus.present;
+  }
+
+  private computeBatterySaverToggleDisabled_(
+      powerSources: PowerSource[]|undefined,
+      lowPowerCharger: boolean): boolean {
+    if (powerSources === undefined) {
+      return true;
+    }
+    return powerSources.length > 0 && !lowPowerCharger;
+  }
+
   private onPowerSourceChange_(): void {
     this.browserProxy_.setPowerSource(this.$.powerSource.value);
   }
@@ -452,6 +488,8 @@
           chrome.settingsPrivate.ControlledBy.DEVICE_POLICY;
     }
     this.adaptiveChargingPref_ = adaptiveChargingPref;
+    this.batterySaverFeatureEnabled_ =
+        powerManagementSettings.batterySaverFeatureEnabled;
   }
 
   /**
diff --git a/chrome/browser/ui/webui/settings/ash/device_power_handler.cc b/chrome/browser/ui/webui/settings/ash/device_power_handler.cc
index 7762497..ea31884a 100644
--- a/chrome/browser/ui/webui/settings/ash/device_power_handler.cc
+++ b/chrome/browser/ui/webui/settings/ash/device_power_handler.cc
@@ -7,6 +7,7 @@
 #include <memory>
 #include <utility>
 
+#include "ash/constants/ash_features.h"
 #include "ash/constants/ash_pref_names.h"
 #include "ash/public/cpp/power_utils.h"
 #include "base/functional/bind.h"
@@ -115,6 +116,8 @@
 const char PowerHandler::kAdaptiveChargingKey[] = "adaptiveCharging";
 const char PowerHandler::kAdaptiveChargingManagedKey[] =
     "adaptiveChargingManaged";
+const char PowerHandler::kBatterySaverFeatureEnabledKey[] =
+    "batterySaverFeatureEnabled";
 
 PowerHandler::TestAPI::TestAPI(PowerHandler* handler) : handler_(handler) {}
 
@@ -401,7 +404,8 @@
       prefs_->GetBoolean(ash::prefs::kPowerAdaptiveChargingEnabled);
   const bool adaptive_charging_managed =
       prefs_->IsManagedPreference(ash::prefs::kPowerAdaptiveChargingEnabled);
-
+  const bool battery_saver_feature_enabled =
+      ash::features::IsBatterySaverAvailable();
   // Don't notify the UI if nothing changed.
   if (!force && ac_idle_info == last_ac_idle_info_ &&
       battery_idle_info == last_battery_idle_info_ &&
@@ -409,7 +413,8 @@
       lid_closed_controlled == last_lid_closed_controlled_ &&
       has_lid == last_has_lid_ &&
       adaptive_charging == last_adaptive_charging_ &&
-      adaptive_charging_managed == last_adaptive_charging_managed_) {
+      adaptive_charging_managed == last_adaptive_charging_managed_ &&
+      battery_saver_feature_enabled == last_battery_saver_feature_enabled_) {
     return;
   }
 
@@ -432,6 +437,7 @@
   dict.Set(kHasLidKey, has_lid);
   dict.Set(kAdaptiveChargingKey, adaptive_charging);
   dict.Set(kAdaptiveChargingManagedKey, adaptive_charging_managed);
+  dict.Set(kBatterySaverFeatureEnabledKey, battery_saver_feature_enabled);
   FireWebUIListener(kPowerManagementSettingsChangedName, dict);
 
   last_ac_idle_info_ = ac_idle_info;
@@ -441,6 +447,7 @@
   last_has_lid_ = has_lid;
   last_adaptive_charging_ = adaptive_charging;
   last_adaptive_charging_managed_ = adaptive_charging_managed;
+  last_battery_saver_feature_enabled_ = battery_saver_feature_enabled;
 }
 
 void PowerHandler::OnGotSwitchStates(
diff --git a/chrome/browser/ui/webui/settings/ash/device_power_handler.h b/chrome/browser/ui/webui/settings/ash/device_power_handler.h
index 7a271c2..4088dc9 100644
--- a/chrome/browser/ui/webui/settings/ash/device_power_handler.h
+++ b/chrome/browser/ui/webui/settings/ash/device_power_handler.h
@@ -53,6 +53,7 @@
   static const char kHasLidKey[];
   static const char kAdaptiveChargingKey[];
   static const char kAdaptiveChargingManagedKey[];
+  static const char kBatterySaverFeatureEnabledKey[];
 
   // Class used by tests to interact with PowerHandler internals.
   class TestAPI {
@@ -186,6 +187,7 @@
   bool last_has_lid_ = true;
   bool last_adaptive_charging_ = false;
   bool last_adaptive_charging_managed_ = false;
+  bool last_battery_saver_feature_enabled_ = false;
 
   base::WeakPtrFactory<PowerHandler> weak_ptr_factory_{this};
 };
diff --git a/chrome/browser/ui/webui/settings/ash/device_power_handler_browsertest.cc b/chrome/browser/ui/webui/settings/ash/device_power_handler_browsertest.cc
index 6c4c386..2da9e6b 100644
--- a/chrome/browser/ui/webui/settings/ash/device_power_handler_browsertest.cc
+++ b/chrome/browser/ui/webui/settings/ash/device_power_handler_browsertest.cc
@@ -78,6 +78,7 @@
     bool has_lid = true;
     bool adaptive_charging = true;
     bool adaptive_charging_managed = false;
+    bool battery_saver_feature_enabled = false;
   };
 
   PowerHandlerTest() = default;
@@ -161,6 +162,8 @@
     dict.Set(PowerHandler::kAdaptiveChargingKey, settings.adaptive_charging);
     dict.Set(PowerHandler::kAdaptiveChargingManagedKey,
              settings.adaptive_charging_managed);
+    dict.Set(PowerHandler::kBatterySaverFeatureEnabledKey,
+             settings.battery_saver_feature_enabled);
     std::string out;
     EXPECT_TRUE(base::JSONWriter::Write(dict, &out));
     return out;
diff --git a/chrome/browser/ui/webui/settings/ash/device_section.cc b/chrome/browser/ui/webui/settings/ash/device_section.cc
index 6c9208b..af260ef 100644
--- a/chrome/browser/ui/webui/settings/ash/device_section.cc
+++ b/chrome/browser/ui/webui/settings/ash/device_section.cc
@@ -1059,11 +1059,16 @@
       {"powerSourceLowPowerCharger",
        IDS_SETTINGS_POWER_SOURCE_LOW_POWER_CHARGER},
       {"powerTitle", IDS_SETTINGS_POWER_TITLE},
+      {"powerBatterySaverLabel", IDS_SETTINGS_POWER_BATTERY_SAVER_LABEL},
+      {"powerBatterySaverSubtext", IDS_SETTINGS_POWER_BATTERY_SAVER_SUBTEXT},
   };
   html_source->AddLocalizedStrings(kPowerStrings);
 
   // TODO(b:216035280): create and link to real "learn more" webpage.
   html_source->AddString("powerAdaptiveChargingLearnMoreUrl", u"about://blank");
+
+  // TODO(b:278957245): create and link to real "learn more" webpage.
+  html_source->AddString("powerBatterySaverLearnMoreUrl", "about://blank");
 }
 
 // Mirrors enum of the same name in enums.xml.
@@ -1394,6 +1399,7 @@
       mojom::Setting::kPowerSource,
       mojom::Setting::kSleepWhenLaptopLidClosed,
       mojom::Setting::kAdaptiveCharging,
+      mojom::Setting::kBatterySaver,
   };
   RegisterNestedSettingBulk(mojom::Subpage::kPower, kPowerSettings, generator);
 }
diff --git a/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom b/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom
index 428d966..02bfa2e4 100644
--- a/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom
+++ b/chrome/browser/ui/webui/settings/chromeos/constants/setting.mojom
@@ -146,6 +146,7 @@
   kKeyboardRemapKeys = 442,
   kChargingSounds = 443,
   kLowBatterySound = 444,
+  kBatterySaver = 445,
 
   // Personalization section.
   kOpenWallpaper = 500,
diff --git a/chrome/test/data/webui/settings/chromeos/device_page/device_page_tests.js b/chrome/test/data/webui/settings/chromeos/device_page/device_page_tests.js
index 58bedaf..84a67c0 100644
--- a/chrome/test/data/webui/settings/chromeos/device_page/device_page_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/device_page/device_page_tests.js
@@ -83,6 +83,13 @@
         },
       },
     },
+    power: {
+      cros_battery_saver_active: {
+        key: 'power.cros_battery_saver_active',
+        type: chrome.settingsPrivate.PrefType.BOOLEAN,
+        value: false,
+      },
+    },
     settings: {
       // TODO(afakhry): Write tests to validate the Night Light slider
       // behavior with 24-hour setting.
@@ -405,22 +412,23 @@
    * @param {boolean} adaptiveChargingManaged
    */
   function sendPowerManagementSettings(
-      possibleAcIdleBehaviors, possibleBatteryIdleBehaviors, currAcIdleBehavior,
-      currBatteryIdleBehavior, acIdleManaged, batteryIdleManaged,
-      lidClosedBehavior, lidClosedControlled, hasLid, adaptiveCharging,
-      adaptiveChargingManaged) {
+      possibleAcIdleBehaviors, possibleBatteryIdleBehaviors,
+      currentAcIdleBehavior, currentBatteryIdleBehavior, acIdleManaged,
+      batteryIdleManaged, lidClosedBehavior, lidClosedControlled, hasLid,
+      adaptiveCharging, adaptiveChargingManaged, batterySaverFeatureEnabled) {
     webUIListenerCallback('power-management-settings-changed', {
-      possibleAcIdleBehaviors: possibleAcIdleBehaviors,
-      possibleBatteryIdleBehaviors: possibleBatteryIdleBehaviors,
-      currentAcIdleBehavior: currAcIdleBehavior,
-      currentBatteryIdleBehavior: currBatteryIdleBehavior,
-      acIdleManaged: acIdleManaged,
-      batteryIdleManaged: batteryIdleManaged,
-      lidClosedBehavior: lidClosedBehavior,
-      lidClosedControlled: lidClosedControlled,
-      hasLid: hasLid,
-      adaptiveCharging: adaptiveCharging,
-      adaptiveChargingManaged: adaptiveChargingManaged,
+      possibleAcIdleBehaviors,
+      possibleBatteryIdleBehaviors,
+      currentAcIdleBehavior,
+      currentBatteryIdleBehavior,
+      acIdleManaged,
+      batteryIdleManaged,
+      lidClosedBehavior,
+      lidClosedControlled,
+      hasLid,
+      adaptiveCharging,
+      adaptiveChargingManaged,
+      batterySaverFeatureEnabled,
     });
     flush();
   }
@@ -2435,6 +2443,7 @@
       let acIdleSelect;
       let lidClosedToggle;
       let adaptiveChargingToggle;
+      let batterySaverToggle;
 
       suiteSetup(function() {
         // Adaptive charging setting should be shown.
@@ -2462,6 +2471,8 @@
               adaptiveChargingToggle =
                   assert(powerPage.shadowRoot.querySelector(
                       '#adaptiveChargingToggle'));
+              batterySaverToggle = assert(
+                  powerPage.shadowRoot.querySelector('#batterySaverToggle'));
 
               assertEquals(
                   1,
@@ -2483,7 +2494,8 @@
                   false /* batteryIdleManaged */, LidClosedBehavior.SUSPEND,
                   false /* lidClosedControlled */, true /* hasLid */,
                   false /* adaptiveCharging */,
-                  false /* adaptiveChargingManaged */);
+                  false /* adaptiveChargingManaged */,
+                  true /* batterySaverFeatureEnabled */);
             });
       });
 
@@ -2502,6 +2514,8 @@
 
         // Power source row is hidden since there's no battery.
         assertTrue(powerSourceRow.hidden);
+        // Battery Saver is also hidden.
+        assertTrue(batterySaverToggle.hidden);
         // Idle settings while on battery and while charging should not be
         // visible if the battery is not present.
         assertEquals(
@@ -2537,6 +2551,8 @@
         // Power sources row is visible but dropdown is hidden.
         assertFalse(powerSourceRow.hidden);
         assertTrue(powerSourceSelect.hidden);
+        // Battery saver should be toggleable when not charging.
+        assertFalse(batterySaverToggle.disabled);
 
         // Attach a dual-role USB device.
         const powerSource = {
@@ -2550,12 +2566,18 @@
         // "Battery" should be selected.
         assertFalse(powerSourceSelect.hidden);
         assertEquals('', powerSourceSelect.value);
+        // We are charging on a non-low power charger, battery saver should be
+        // grayed out.
+        assertTrue(batterySaverToggle.disabled);
 
         // Select the power source.
         setPowerSources([powerSource], powerSource.id, true);
         flush();
         assertFalse(powerSourceSelect.hidden);
         assertEquals(powerSource.id, powerSourceSelect.value);
+        // Charging, but on a low-power charger, battery saver should be
+        // toggleable.
+        assertFalse(batterySaverToggle.disabled);
 
         // Send another power source; the first should still be selected.
         const otherPowerSource = Object.assign({}, powerSource);
@@ -2564,6 +2586,7 @@
         flush();
         assertFalse(powerSourceSelect.hidden);
         assertEquals(powerSource.id, powerSourceSelect.value);
+        assertFalse(batterySaverToggle.disabled);
       });
 
       test('choose power source', function() {
@@ -2656,8 +2679,8 @@
               IdleBehavior.DISPLAY_OFF, IdleBehavior.DISPLAY_OFF,
               false /* acIdleManaged */, false /* batteryIdleManaged */,
               lidBehavior, false /* lidClosedControlled */, true /* hasLid */,
-              false /* adaptiveCharging */,
-              false /* adaptiveChargingManaged */);
+              false /* adaptiveCharging */, false /* adaptiveChargingManaged */,
+              true /* batterySaverFeatureEnabled */);
         };
 
         sendLid(LidClosedBehavior.SUSPEND);
@@ -2700,7 +2723,8 @@
                      LidClosedBehavior.DO_NOTHING,
                      false /* lidClosedControlled */, true /* hasLid */,
                      false /* adaptiveCharging */,
-                     false /* adaptiveChargingManaged */);
+                     false /* adaptiveChargingManaged */,
+                     true /* batterySaverFeatureEnabled */);
                  microTask.run(resolve);
                })
             .then(function() {
@@ -2758,7 +2782,8 @@
                   true /* acIdleManaged */, true /* batteryIdleManaged */,
                   LidClosedBehavior.DO_NOTHING, false /* lidClosedControlled */,
                   true /* hasLid */, false /* adaptiveCharging */,
-                  false /* adaptiveChargingManaged */);
+                  false /* adaptiveChargingManaged */,
+                  true /* batterySaverFeatureEnabled */);
               return new Promise(function(resolve) {
                 microTask.run(resolve);
               });
@@ -2800,7 +2825,8 @@
                      LidClosedBehavior.DO_NOTHING,
                      false /* lidClosedControlled */, true /* hasLid */,
                      false /* adaptiveCharging */,
-                     false /* adaptiveChargingManaged */);
+                     false /* adaptiveChargingManaged */,
+                     true /* batterySaverFeatureEnabled */);
                  microTask.run(resolve);
                })
             .then(function() {
@@ -2857,7 +2883,8 @@
                   false /* acIdleManaged */, false /* batteryIdleManaged */,
                   LidClosedBehavior.SUSPEND, false /* lidClosedControlled */,
                   true /* hasLid */, false /* adaptiveCharging */,
-                  false /* adaptiveChargingManaged */);
+                  false /* adaptiveChargingManaged */,
+                  true /* batterySaverFeatureEnabled */);
               return new Promise(function(resolve) {
                 microTask.run(resolve);
               });
@@ -2910,7 +2937,8 @@
                      LidClosedBehavior.SHUT_DOWN,
                      true /* lidClosedControlled */, true /* hasLid */,
                      false /* adaptiveCharging */,
-                     false /* adaptiveChargingManaged */);
+                     false /* adaptiveChargingManaged */,
+                     true /* batterySaverFeatureEnabled */);
                  microTask.run(resolve);
                })
             .then(function() {
@@ -2946,7 +2974,8 @@
                   LidClosedBehavior.STOP_SESSION,
                   true /* lidClosedControlled */, true /* hasLid */,
                   false /* adaptiveCharging */,
-                  false /* adaptiveChargingManaged */);
+                  false /* adaptiveChargingManaged */,
+                  true /* batterySaverFeatureEnabled */);
               return new Promise(function(resolve) {
                 microTask.run(resolve);
               });
@@ -2997,7 +3026,8 @@
                      false /* batteryIdleManaged */, LidClosedBehavior.SUSPEND,
                      false /* lidClosedControlled */, false /* hasLid */,
                      false /* adaptiveCharging */,
-                     false /* adaptiveChargingManaged */);
+                     false /* adaptiveChargingManaged */,
+                     true /* batterySaverFeatureEnabled */);
                  microTask.run(resolve);
                })
             .then(function() {
@@ -3054,7 +3084,8 @@
             false /* acIdleManaged */, false /* batteryIdleManaged */,
             LidClosedBehavior.SUSPEND, false /* lidClosedControlled */,
             true /* hasLid */, true /* adaptiveCharging */,
-            true /* adaptiveCharingManaged */);
+            true /* adaptiveCharingManaged */,
+            true /* batterySaverFeatureEnabled */);
 
         assertTrue(adaptiveChargingToggle.shadowRoot.querySelector('cr-toggle')
                        .checked);
@@ -3074,6 +3105,78 @@
             adaptiveChargingToggle.shadowRoot.querySelector('cr-toggle'),
             'Adaptive charging toggle');
       });
+
+      test('Battery Saver hidden when feature disabled', () => {
+        sendPowerManagementSettings(
+            [
+              IdleBehavior.DISPLAY_OFF_SLEEP,
+              IdleBehavior.DISPLAY_OFF,
+              IdleBehavior.DISPLAY_ON,
+            ],
+            [
+              IdleBehavior.DISPLAY_OFF_SLEEP,
+              IdleBehavior.DISPLAY_OFF,
+              IdleBehavior.DISPLAY_ON,
+            ],
+            IdleBehavior.DISPLAY_OFF_SLEEP, IdleBehavior.DISPLAY_OFF_SLEEP,
+            false /* acIdleManaged */, false /* batteryIdleManaged */,
+            LidClosedBehavior.SUSPEND, false /* lidClosedControlled */,
+            true /* hasLid */, false /* adaptiveCharging */,
+            false /* adaptiveChargingManaged */,
+            false /* batterySaverFeatureEnabled */);
+
+        assertTrue(batterySaverToggle.hidden);
+      });
+
+      test('Battery Saver toggleable', () => {
+        // Battery is present.
+        webUIListenerCallback('battery-status-changed', {
+          present: true,
+          charging: false,
+          calculating: false,
+          percent: 50,
+          statusText: '5 hours left',
+        });
+        // There are no power sources.
+        setPowerSources([], '', false);
+        // Battery saver feature is enabled.
+        sendPowerManagementSettings(
+            [
+              IdleBehavior.DISPLAY_OFF_SLEEP,
+              IdleBehavior.DISPLAY_OFF,
+              IdleBehavior.DISPLAY_ON,
+            ],
+            [
+              IdleBehavior.DISPLAY_OFF_SLEEP,
+              IdleBehavior.DISPLAY_OFF,
+              IdleBehavior.DISPLAY_ON,
+            ],
+            IdleBehavior.DISPLAY_OFF_SLEEP, IdleBehavior.DISPLAY_OFF_SLEEP,
+            false /* acIdleManaged */, false /* batteryIdleManaged */,
+            LidClosedBehavior.SUSPEND, false /* lidClosedControlled */,
+            true /* hasLid */, false /* adaptiveCharging */,
+            false /* adaptiveChargingManaged */,
+            true /* batterySaverFeatureEnabled */);
+
+        // Battery saver should be visible and toggleable.
+        assertFalse(batterySaverToggle.hidden);
+        assertFalse(batterySaverToggle.disabled);
+      });
+
+      test('Battery Saver updates when pref updates', () => {
+        function setPref(value) {
+          const newPrefs = getFakePrefs();
+          newPrefs.power.cros_battery_saver_active.value = value;
+          powerPage.prefs = newPrefs;
+          flush();
+        }
+
+        setPref(true);
+        assertTrue(batterySaverToggle.checked);
+
+        setPref(false);
+        assertFalse(batterySaverToggle.checked);
+      });
     });
   });
 
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 2967ada..edce33b 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -78462,6 +78462,7 @@
   <int value="442" label="Keyboard: Remap Keys"/>
   <int value="443" label="Charging sounds: On/Off"/>
   <int value="444" label="Low battery sound: On/Off"/>
+  <int value="445" label="Battery Saver: On/Off"/>
   <int value="500" label="Open Wallpaper"/>
   <int value="501" label="Ambient Mode On/Off"/>
   <int value="502" label="Ambient Mode Source"/>