diff --git a/chrome/browser/ash/crosapi/BUILD.gn b/chrome/browser/ash/crosapi/BUILD.gn index 96c9044ed2841a..59fff0c633bf50 100644 --- a/chrome/browser/ash/crosapi/BUILD.gn +++ b/chrome/browser/ash/crosapi/BUILD.gn @@ -336,6 +336,7 @@ source_set("crosapi") { "//extensions/browser/api/audio", "//extensions/browser/api/automation_internal", "//extensions/browser/api/networking_private", + "//extensions/browser/api/power", "//extensions/common", "//media/mojo/mojom/stable:stable_video_decoder", "//printing/backend", diff --git a/chrome/browser/ash/crosapi/power_ash.cc b/chrome/browser/ash/crosapi/power_ash.cc index d65240963db070..8e48bbcddd0320 100644 --- a/chrome/browser/ash/crosapi/power_ash.cc +++ b/chrome/browser/ash/crosapi/power_ash.cc @@ -10,6 +10,7 @@ #include "base/task/sequenced_task_runner.h" #include "base/task/single_thread_task_runner.h" #include "base/task/thread_pool.h" +#include "extensions/browser/api/power/activity_reporter_delegate.h" #include "services/device/wake_lock/power_save_blocker/power_save_blocker.h" namespace crosapi { @@ -38,6 +39,10 @@ void PowerAsh::AddPowerSaveBlocker( type, reason, description, main_task_runner_, file_task_runner_); } +void PowerAsh::ReportActivity() { + extensions::ActivityReporterDelegate::GetDelegate()->ReportActivity(); +} + void PowerAsh::OnLockDisconnect(mojo::RemoteSetElementId id) { power_save_blockers_.erase(id); } diff --git a/chrome/browser/ash/crosapi/power_ash.h b/chrome/browser/ash/crosapi/power_ash.h index 8eed5d150a2bf3..3ff7a6606913e5 100644 --- a/chrome/browser/ash/crosapi/power_ash.h +++ b/chrome/browser/ash/crosapi/power_ash.h @@ -45,6 +45,7 @@ class PowerAsh : public mojom::Power { device::mojom::WakeLockType type, device::mojom::WakeLockReason reason, const std::string& description) override; + void ReportActivity() override; private: // Handlers for disconnection of |lock| from AddPowerSaveBlocker(), diff --git a/chrome/browser/ash/crosapi/power_ash_apitest.cc b/chrome/browser/ash/crosapi/power_ash_apitest.cc new file mode 100644 index 00000000000000..966744b8fa03dc --- /dev/null +++ b/chrome/browser/ash/crosapi/power_ash_apitest.cc @@ -0,0 +1,83 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/extensions/extension_apitest.h" +#include "content/public/test/browser_test.h" +#include "extensions/browser/api/idle/idle_manager.h" +#include "extensions/browser/api/idle/idle_manager_factory.h" +#include "extensions/browser/background_script_executor.h" +#include "extensions/common/extension.h" +#include "extensions/test/extension_test_message_listener.h" +#include "extensions/test/result_catcher.h" +#include "ui/base/idle/scoped_set_idle_state.h" + +namespace extensions { + +namespace { + +constexpr char kExtensionRelativePath[] = "power/report_activity"; +constexpr char kExtensionId[] = "dibbenaepdnglcjpgjmnefmjccpinang"; + +} // namespace + +using ContextType = ExtensionBrowserTest::ContextType; + +class PowerApiTest : public ExtensionApiTest, + public testing::WithParamInterface { + public: + PowerApiTest() : ExtensionApiTest(GetParam()) {} +}; + +INSTANTIATE_TEST_SUITE_P(PersistentBackground, + PowerApiTest, + ::testing::Values(ContextType::kPersistentBackground)); + +INSTANTIATE_TEST_SUITE_P(ServiceWorker, + PowerApiTest, + ::testing::Values(ContextType::kServiceWorker)); + +// Verifies that chrome.power.reportActivity() correctly reports a user activity +// by observing idle states. +IN_PROC_BROWSER_TEST_P(PowerApiTest, + ReportActivityChangesIdleStateFromIdleToActive) { + ResultCatcher catcher; + + ExtensionTestMessageListener ready_listener("ready"); + ready_listener.set_extension_id(kExtensionId); + + LoadExtension(test_data_dir_.AppendASCII(kExtensionRelativePath)); + + // Wait for idle state listener for be set up. + ASSERT_TRUE(ready_listener.WaitUntilSatisfied()); + + { + // Wait for the state to change to idle. + ExtensionTestMessageListener idle_listener("idle"); + ui::ScopedSetIdleState idle(ui::IDLE_STATE_IDLE); + ASSERT_TRUE(idle_listener.WaitUntilSatisfied()); + + // Allow the idle state to go out of scope to reset it. + // Otherwise the QueryState() call is always going to return + // the test state, even though it actually changed. + } + + auto* idle_manager = IdleManagerFactory::GetForBrowserContext(profile()); + auto threshold = idle_manager->GetThresholdForTest(kExtensionId); + ASSERT_EQ(idle_manager->QueryState(threshold), ui::IDLE_STATE_IDLE); + + // Report activity. + BackgroundScriptExecutor script_executor(profile()); + constexpr char kReportActivityScript[] = R"(chrome.power.reportActivity();)"; + script_executor.BackgroundScriptExecutor::ExecuteScriptAsync( + kExtensionId, kReportActivityScript, + BackgroundScriptExecutor::ResultCapture::kNone); + + // The test succeeds if the state goes from idle to active. + ASSERT_TRUE(catcher.GetNextResult()); + + // Test that the actual state is active. + ASSERT_EQ(idle_manager->QueryState(threshold), ui::IDLE_STATE_ACTIVE); +} + +} // namespace extensions diff --git a/chrome/browser/lacros/power_api_lacros_browsertest.cc b/chrome/browser/lacros/power_api_lacros_browsertest.cc new file mode 100644 index 00000000000000..09e106a60da378 --- /dev/null +++ b/chrome/browser/lacros/power_api_lacros_browsertest.cc @@ -0,0 +1,124 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/test/base/in_process_browser_test.h" +#include "chromeos/crosapi/mojom/idle_service.mojom.h" +#include "chromeos/crosapi/mojom/power.mojom.h" +#include "chromeos/lacros/lacros_service.h" +#include "content/public/test/browser_test.h" +#include "mojo/public/cpp/bindings/receiver.h" + +namespace { + +// Verifies that all required Crosapi dependencies of the test are available. +bool ValidateCrosapi() { + auto* lacros_service = chromeos::LacrosService::Get(); + if (!lacros_service) { + return false; + } + + if (!lacros_service->IsAvailable()) { + return false; + } + + int interface_version = chromeos::LacrosService::Get()->GetInterfaceVersion( + crosapi::mojom::Power::Uuid_); + if (interface_version < + int(crosapi::mojom::Power::kReportActivityMinVersion)) { + return false; + } + + if (!lacros_service->IsAvailable()) { + return false; + } + + return true; +} + +class TestIdleInfoObserver : public crosapi::mojom::IdleInfoObserver { + public: + explicit TestIdleInfoObserver(base::RepeatingClosure callback) + : callback_(callback) {} + + TestIdleInfoObserver(const TestIdleInfoObserver&) = delete; + TestIdleInfoObserver& operator=(const TestIdleInfoObserver&) = delete; + + void Start() { + chromeos::LacrosService::Get() + ->GetRemote() + ->AddIdleInfoObserver(receiver_.BindNewPipeAndPassRemoteWithVersion()); + } + + base::TimeTicks GetLastUserActivityTime() { return last_activity_time_; } + + private: + // crosapi::mojom::IdleInfoObserver: + void OnIdleInfoChanged(crosapi::mojom::IdleInfoPtr info) override { + last_activity_time_ = info->last_activity_time; + callback_.Run(); + } + + base::RepeatingClosure callback_; + base::TimeTicks last_activity_time_; + mojo::Receiver receiver_{this}; +}; + +class PowerApiLacrosBrowserTest : public InProcessBrowserTest { + protected: + PowerApiLacrosBrowserTest() = default; + + PowerApiLacrosBrowserTest(const PowerApiLacrosBrowserTest&) = delete; + PowerApiLacrosBrowserTest& operator=(const PowerApiLacrosBrowserTest&) = + delete; + ~PowerApiLacrosBrowserTest() override = default; +}; + +// Calling ReportActivity() will update the last user activity. +IN_PROC_BROWSER_TEST_F(PowerApiLacrosBrowserTest, + ReportActivityUpdatesLastUserActivity) { + if (!ValidateCrosapi()) { + return; + } + + // Report user activity and remember the time before. + base::TimeTicks initial_time = base::TimeTicks::Now(); + chromeos::LacrosService::Get() + ->GetRemote() + .get() + ->ReportActivity(); + + // TestIdleInfoObserver will receive the last user activity when added via a + // call to its OnIdleInfoChanged() method. + base::RunLoop run_loop; + TestIdleInfoObserver observer(run_loop.QuitClosure()); + observer.Start(); + run_loop.Run(); + + // User activity was reported after calling ReportActivity(). + EXPECT_GT(observer.GetLastUserActivityTime(), initial_time); +} + +// Checks that the reported last user activity does not update without a +// ReportActivity() call. +IN_PROC_BROWSER_TEST_F(PowerApiLacrosBrowserTest, + LastUserActivityNotUpdatedWithoutCall) { + if (!ValidateCrosapi()) { + return; + } + + // Save initial time to check no activity was reported during the test. + base::TimeTicks initial_time = base::TimeTicks::Now(); + + // TestIdleInfoObserver will receive the last user activity when added via a + // call to its OnIdleInfoChanged() method. + base::RunLoop run_loop; + TestIdleInfoObserver observer(run_loop.QuitClosure()); + observer.Start(); + run_loop.Run(); + + // No user activity reported without calling ReportActivity(). + EXPECT_LT(observer.GetLastUserActivityTime(), initial_time); +} + +} // namespace diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index 2f8020d7eafee4..d61de0cb916ae8 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn @@ -3679,6 +3679,7 @@ if (!is_android) { "../browser/ash/crosapi/browser_data_migrator_browsertest.cc", "../browser/ash/crosapi/media_ui_ash_browsertest.cc", "../browser/ash/crosapi/network_settings_service_ash_browsertest.cc", + "../browser/ash/crosapi/power_ash_apitest.cc", "../browser/ash/crosapi/screen_manager_ash_browsertest.cc", "../browser/ash/crosapi/video_conference_ash_browsertest.cc", "../browser/ash/crosapi/vpn_extension_observer_ash_browsertest.cc", @@ -5003,6 +5004,7 @@ if (is_chromeos_lacros) { "../browser/lacros/overview_lacros_browsertest.cc", "../browser/lacros/policy_ui_lacros_browsertest.cc", "../browser/lacros/popup_lacros_browsertest.cc", + "../browser/lacros/power_api_lacros_browsertest.cc", "../browser/lacros/screen_manager_lacros_browsertest.cc", "../browser/lacros/smart_reader_lacros_browsertest.cc", "../browser/lacros/tab_scrubber_lacros_browsertest.cc", diff --git a/chrome/test/data/extensions/api_test/power/report_activity/manifest.json b/chrome/test/data/extensions/api_test/power/report_activity/manifest.json new file mode 100644 index 00000000000000..f6011e3291be3d --- /dev/null +++ b/chrome/test/data/extensions/api_test/power/report_activity/manifest.json @@ -0,0 +1,17 @@ +{ + "name": "chrome.power.reportActivity() test extension (ID dibbenaepdnglcjpgjmnefmjccpinang)", + "key": "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCb0DzjK+cmP0/ULDF4z7mS8wniW1e0fo+HMhrznMqsahgrX/zYzc0fGZDX5dmzE3FQ9c1a4BdqWCaxI4q3yQmmNR+xKYUWaYuaTQAI1RPIUqZK3yps+2TUqMkfa1Da9t6pBGlzNXwKwsrvDdP55oXPlhtXiJZ0Qq+0AtRHhbi7/kzR7EpRjqhfWmJLYovSfBP9ZLSvzO/GhBIxlEvY3KGdH9fv4xEUR0AKV0TmSlEV/3fLY6tF5vFH8+a6Z5KCDhI6Df0nYl/bCbplAt6geHBhBBW+8NgW00b/jcij9gWilUvUewgV9fwYgTzhNAd5wYKAclAKkKx3+EK8rw7t5gMHAgMBAAECggEAD1zPFArucHv05VOHe9o8pCYoXFQV6ABD1dhKhf3N5y/9ZWbCaac+STRebpsFWuhY14WqE1gIQaj4BPvJypMcy3FuHYFIKPc2bNFDZpuI/CBXWwcj32OYrnWx1tI1l6aYOjaVexF+mG+vozWhfSs4bZ48NeY2sgx9LGohqfz07jwIoEf5MxW4pSF0QxRX+3yBeUnEKmgSg+5RPOiaokcUVbtOAJ/27e0PYxtKK6GiDTWGnz4cV+ylqQEEQxg4zH4+IjUjcakfi0hdkEKMzLvCdXyWLGYbTeK4z2fFgwCWWgE5Sb4cetxLgJHHqnEnC1VUlAU6fXJoGS+lupTA/hG80QKBgQDb1YfcpastkxOxUOcweoCCtHT8K/IckAxRCE0hz4cKt4KSmxDWhqVHZwNHg7bKchQgR/GPM57GvOiN//8fqFSNbjE6UKqQEkvxRlUZSVfLKnqp55NWATgOrsRVP4zGN4vp3HTNnLK5rSgMTfsAvLwXckaEKa8PfV550t77eZaMSQKBgQC1cm5Tvt5utQDKBb/DC2u92bSKHX+I0p+JrGuyeSX/MWD0uWMJtVFBpdmcd/26iIUsTJJjdk4gGQXGuv8OGW3gN6qDNOGgkcteGW51YSRZQUxG/zJqTPBVAeJIDw5m+SoHnTd3EHfYTPMz+TUVuKKWW4AY19o4/D+T0Njtgc30zwKBgQCzl4pas+1YWiNoZJO6gxmhrhM0QCKXOwcU3BdHW+cS1kCRzKTA3VcBMiL3tZ8LXI8coCmzt5QrAAFsoIqoLjiFIlSNM4FkGc542eCDu+tBSv7S9yDizjQhqp0yl6xF8vMWpse1giJwlgl1o+8+9vEipnT9W6BsoxsfoyoPf5GCIQKBgQCnGwU6wW1PXgmlBRhvhCQtC8Q28yXSl8/wVkg1bVeuKAbYO6hXd5KnOBwq+2NFGJg1jSvAyGL5MrJcLTna/VZxCjNSdTBa1gsK1bciCV4ViYq7VCpVNhic4YhJvvwPn+eybXt4f0UwguX09s2J/KpYeVqHBrxmgQH4m349dq98hwKBgC2MFgDw9/4r8GUgYxS5bAvAtegqQv4Rl3wzTOOe/KO95zukmqbFI93fI9ksTYDZ2t5n8jE6qfKms0W7ItJ3irb0oQHcHyL8vhl8rAZVkffmDyGU7pYMm3bQRtL029fKnwJ9miySj2+hAObuFZg6feum5z8fztn2Whoz8dujCvl+", + "version": "1.0", + "manifest_version": 2, + "description": "chrome.power.reportActivity API test", + "background": { + "scripts": [ + "test.js" + ], + "persistent": true + }, + "permissions": [ + "idle", + "power" + ] +} diff --git a/chrome/test/data/extensions/api_test/power/report_activity/test.js b/chrome/test/data/extensions/api_test/power/report_activity/test.js new file mode 100644 index 00000000000000..2ef67f35a8757b --- /dev/null +++ b/chrome/test/data/extensions/api_test/power/report_activity/test.js @@ -0,0 +1,19 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +let wasIdle = false; +chrome.idle.onStateChanged.addListener((newState) => { + // First state is set to be idle from the Chrome side of the test. We report + // an activity once the state is idle. + if (newState === 'idle') { + wasIdle = true; + chrome.test.sendMessage('idle'); + } + // Only succeed a test if we go from "idle" to "active". + else if (newState === 'active' && wasIdle) { + chrome.test.succeed(); + } +}); + +chrome.test.sendMessage('ready'); diff --git a/chrome/test/data/extensions/api_test/stubs_app/background.js b/chrome/test/data/extensions/api_test/stubs_app/background.js index 5463b3cb7b1499..a4f7b6c866a4dc 100644 --- a/chrome/test/data/extensions/api_test/stubs_app/background.js +++ b/chrome/test/data/extensions/api_test/stubs_app/background.js @@ -24,10 +24,11 @@ function getApiPaths() { return; // Pieces of the module don't inherit from Array/Object. Array.prototype.forEach.call(section, function(entry) { - // Skip idle.getAutoLockDelay(), since it's restricted to certain - // platforms. - // TODO(https://crbug.com/921466) - if (entry.name != "getAutoLockDelay") { + // Skip idle.getAutoLockDelay() and power.reportActivity() since they + // are restricted to certain platforms. + // TODO(https://crbug.com/921466) + if (entry.name != 'getAutoLockDelay' && + entry.name != 'reportActivity') { apiPaths.push(namespace + "." + entry.name); } }); diff --git a/chromeos/crosapi/mojom/power.mojom b/chromeos/crosapi/mojom/power.mojom index 699dd33830c4ea..3095f3aceb4f1b 100644 --- a/chromeos/crosapi/mojom/power.mojom +++ b/chromeos/crosapi/mojom/power.mojom @@ -12,7 +12,9 @@ import "services/device/public/mojom/wake_lock.mojom"; interface PowerWakeLock { }; -// Crosapi support for chrome.system.display extensions API. +// Crosapi support for chrome.power extension API. +// +// Next MinVersion: 2 [Stable, Uuid="878f8be2-ad59-4cc6-84ef-abc5102da696"] interface Power { // Creates a PowerSaveBlocker that lasts until |lock| disconnects. @@ -20,4 +22,10 @@ interface Power { device.mojom.WakeLockType type, device.mojom.WakeLockReason reason, string description); + + // Reports a user activity in order to awake the screen from a dimmed or + // turned off state or from a screensaver. Exits the screensaver if it is + // currently active. + [MinVersion=1] + ReportActivity@1(); }; diff --git a/extensions/BUILD.gn b/extensions/BUILD.gn index eb5b6dc41f187b..dc28fd70a5a4ce 100644 --- a/extensions/BUILD.gn +++ b/extensions/BUILD.gn @@ -372,6 +372,7 @@ source_set("chrome_extensions_browsertests_sources") { "//third_party/widevine/cdm:headers", "//ui/accessibility:test_support", "//ui/base:test_support", + "//ui/base/idle:test_support", "//ui/compositor:test_support", "//ui/resources", "//ui/web_dialogs:test_support", diff --git a/extensions/browser/api/power/BUILD.gn b/extensions/browser/api/power/BUILD.gn index 0573516048a520..a96b72020ed14f 100644 --- a/extensions/browser/api/power/BUILD.gn +++ b/extensions/browser/api/power/BUILD.gn @@ -2,6 +2,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import("//build/config/chromeos/ui_mode.gni") import("//extensions/buildflags/buildflags.gni") assert(enable_extensions, @@ -24,5 +25,30 @@ source_set("power") { "//services/device/public/mojom", ] + if (is_chromeos) { + sources += [ + "activity_reporter_delegate.cc", + "activity_reporter_delegate.h", + ] + + deps += [ "//build:chromeos_buildflags" ] + + if (is_chromeos_ash) { + sources += [ + "activity_reporter_delegate_ash.cc", + "activity_reporter_delegate_ash.h", + ] + } else if (is_chromeos_lacros) { + sources += [ + "activity_reporter_delegate_lacros.cc", + "activity_reporter_delegate_lacros.h", + ] + deps += [ + "//chromeos/crosapi/mojom", + "//chromeos/lacros", + ] + } + } + public_deps = [ "//extensions/browser:browser_sources" ] } diff --git a/extensions/browser/api/power/activity_reporter_delegate.cc b/extensions/browser/api/power/activity_reporter_delegate.cc new file mode 100644 index 00000000000000..453ec776fea406 --- /dev/null +++ b/extensions/browser/api/power/activity_reporter_delegate.cc @@ -0,0 +1,30 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/browser/api/power/activity_reporter_delegate.h" + +#include "build/build_config.h" +#include "build/chromeos_buildflags.h" + +#if BUILDFLAG(IS_CHROMEOS_ASH) +#include "extensions/browser/api/power/activity_reporter_delegate_ash.h" +#elif BUILDFLAG(IS_CHROMEOS_LACROS) +#include "extensions/browser/api/power/activity_reporter_delegate_lacros.h" +#endif + +namespace extensions { + +// static +std::unique_ptr +ActivityReporterDelegate::GetDelegate() { +#if BUILDFLAG(IS_CHROMEOS_ASH) + return std::make_unique(); +#elif BUILDFLAG(IS_CHROMEOS_LACROS) + return std::make_unique(); +#else +#error Unsupported platform +#endif +} + +} // namespace extensions diff --git a/extensions/browser/api/power/activity_reporter_delegate.h b/extensions/browser/api/power/activity_reporter_delegate.h new file mode 100644 index 00000000000000..53d2370baa0668 --- /dev/null +++ b/extensions/browser/api/power/activity_reporter_delegate.h @@ -0,0 +1,24 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_BROWSER_API_POWER_ACTIVITY_REPORTER_DELEGATE_H_ +#define EXTENSIONS_BROWSER_API_POWER_ACTIVITY_REPORTER_DELEGATE_H_ + +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace extensions { + +// Base class for platform dependent chrome.power.reportActivity() +// implementations. +class ActivityReporterDelegate { + public: + virtual ~ActivityReporterDelegate() = default; + + static std::unique_ptr GetDelegate(); + virtual absl::optional ReportActivity() const = 0; +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_POWER_ACTIVITY_REPORTER_DELEGATE_H_ diff --git a/extensions/browser/api/power/activity_reporter_delegate_ash.cc b/extensions/browser/api/power/activity_reporter_delegate_ash.cc new file mode 100644 index 00000000000000..5e3bd873d8f257 --- /dev/null +++ b/extensions/browser/api/power/activity_reporter_delegate_ash.cc @@ -0,0 +1,21 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/browser/api/power/activity_reporter_delegate_ash.h" + +#include "ui/base/user_activity/user_activity_detector.h" + +namespace extensions { + +ActivityReporterDelegateAsh::ActivityReporterDelegateAsh() = default; + +ActivityReporterDelegateAsh::~ActivityReporterDelegateAsh() = default; + +absl::optional ActivityReporterDelegateAsh::ReportActivity() + const { + ui::UserActivityDetector::Get()->HandleExternalUserActivity(); + return absl::nullopt; +} + +} // namespace extensions diff --git a/extensions/browser/api/power/activity_reporter_delegate_ash.h b/extensions/browser/api/power/activity_reporter_delegate_ash.h new file mode 100644 index 00000000000000..4d8b7cbfae2ce1 --- /dev/null +++ b/extensions/browser/api/power/activity_reporter_delegate_ash.h @@ -0,0 +1,27 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_BROWSER_API_POWER_ACTIVITY_REPORTER_DELEGATE_ASH_H_ +#define EXTENSIONS_BROWSER_API_POWER_ACTIVITY_REPORTER_DELEGATE_ASH_H_ + +#include "extensions/browser/api/power/activity_reporter_delegate.h" + +namespace extensions { + +// Ash ActivityReporterDelegate implementation. +class ActivityReporterDelegateAsh : public ActivityReporterDelegate { + public: + ActivityReporterDelegateAsh(); + ActivityReporterDelegateAsh(const ActivityReporterDelegateAsh&) = delete; + ActivityReporterDelegateAsh& operator=(const ActivityReporterDelegateAsh&) = + delete; + ~ActivityReporterDelegateAsh() override; + + // PowerApi + absl::optional ReportActivity() const override; +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_POWER_ACTIVITY_REPORTER_DELEGATE_ASH_H_ diff --git a/extensions/browser/api/power/activity_reporter_delegate_lacros.cc b/extensions/browser/api/power/activity_reporter_delegate_lacros.cc new file mode 100644 index 00000000000000..04acfb518b9671 --- /dev/null +++ b/extensions/browser/api/power/activity_reporter_delegate_lacros.cc @@ -0,0 +1,59 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/browser/api/power/activity_reporter_delegate_lacros.h" + +#include "base/values.h" +#include "chromeos/crosapi/mojom/power.mojom.h" +#include "chromeos/lacros/lacros_service.h" + +namespace { + +const char kUnsupportedByAsh[] = "Unsupported by ash"; + +crosapi::mojom::Power* GetPowerApi() { + return chromeos::LacrosService::Get() + ->GetRemote() + .get(); +} + +// Performs common crosapi validation. These errors are not caused by the +// extension so they are considered recoverable. Returns an error message on +// error, or nullopt on success. +// |min_version| is the minimum version of the ash implementation of +// crosapi::mojom::Power necessary to run a specific API method. +absl::optional ValidateCrosapi(int min_version) { + if (!chromeos::LacrosService::Get()->IsAvailable()) { + return kUnsupportedByAsh; + } + + int interface_version = chromeos::LacrosService::Get()->GetInterfaceVersion( + crosapi::mojom::Power::Uuid_); + if (interface_version < min_version) { + return kUnsupportedByAsh; + } + + return absl::nullopt; +} + +} // namespace + +namespace extensions { + +ActivityReporterDelegateLacros::ActivityReporterDelegateLacros() = default; + +ActivityReporterDelegateLacros::~ActivityReporterDelegateLacros() = default; + +absl::optional ActivityReporterDelegateLacros::ReportActivity() + const { + absl::optional error = + ValidateCrosapi(crosapi::mojom::Power::kReportActivityMinVersion); + if (error.has_value()) { + return error; + } + GetPowerApi()->ReportActivity(); + return absl::nullopt; +} + +} // namespace extensions diff --git a/extensions/browser/api/power/activity_reporter_delegate_lacros.h b/extensions/browser/api/power/activity_reporter_delegate_lacros.h new file mode 100644 index 00000000000000..c42b81cd91a8f1 --- /dev/null +++ b/extensions/browser/api/power/activity_reporter_delegate_lacros.h @@ -0,0 +1,28 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_BROWSER_API_POWER_ACTIVITY_REPORTER_DELEGATE_LACROS_H_ +#define EXTENSIONS_BROWSER_API_POWER_ACTIVITY_REPORTER_DELEGATE_LACROS_H_ + +#include "extensions/browser/api/power/activity_reporter_delegate.h" + +namespace extensions { + +// Lacros ActivityReporterDelegate implementation. +class ActivityReporterDelegateLacros : public ActivityReporterDelegate { + public: + ActivityReporterDelegateLacros(); + ActivityReporterDelegateLacros(const ActivityReporterDelegateLacros&) = + delete; + ActivityReporterDelegateLacros& operator=( + const ActivityReporterDelegateLacros&) = delete; + ~ActivityReporterDelegateLacros() override; + + // PowerApi + absl::optional ReportActivity() const override; +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_API_POWER_ACTIVITY_REPORTER_DELEGATE_LACROS_H_ diff --git a/extensions/browser/api/power/power_api.cc b/extensions/browser/api/power/power_api.cc index 65b8fcb754622a..4cde16ddbc35d8 100644 --- a/extensions/browser/api/power/power_api.cc +++ b/extensions/browser/api/power/power_api.cc @@ -7,6 +7,7 @@ #include "base/functional/bind.h" #include "base/lazy_instance.h" #include "content/public/browser/device_service.h" +#include "extensions/browser/api/power/activity_reporter_delegate.h" #include "extensions/browser/extension_registry.h" #include "extensions/common/api/power.h" #include "extensions/common/extension.h" @@ -48,6 +49,17 @@ ExtensionFunction::ResponseAction PowerReleaseKeepAwakeFunction::Run() { return RespondNow(NoArguments()); } +#if BUILDFLAG(IS_CHROMEOS) +ExtensionFunction::ResponseAction PowerReportActivityFunction::Run() { + absl::optional error = + extensions::ActivityReporterDelegate::GetDelegate()->ReportActivity(); + if (error.has_value()) { + return RespondNow(Error(error.value())); + } + return RespondNow(NoArguments()); +} +#endif // BUILDFLAG(IS_CHROMEOS) + // static PowerAPI* PowerAPI::Get(content::BrowserContext* context) { return BrowserContextKeyedAPIFactory::Get(context); diff --git a/extensions/browser/api/power/power_api.h b/extensions/browser/api/power/power_api.h index 7c40da9c2ec4bc..9a4c85a9512617 100644 --- a/extensions/browser/api/power/power_api.h +++ b/extensions/browser/api/power/power_api.h @@ -11,6 +11,7 @@ #include "base/functional/callback.h" #include "base/memory/raw_ptr.h" +#include "build/chromeos_buildflags.h" #include "extensions/browser/browser_context_keyed_api_factory.h" #include "extensions/browser/extension_function.h" #include "extensions/browser/extension_registry_observer.h" @@ -48,6 +49,20 @@ class PowerReleaseKeepAwakeFunction : public ExtensionFunction { ResponseAction Run() override; }; +#if BUILDFLAG(IS_CHROMEOS) +// Implementation of the chrome.power.reportActivity API. +class PowerReportActivityFunction : public ExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("power.reportActivity", POWER_REPORTACTIVITY) + + protected: + ~PowerReportActivityFunction() override = default; + + // ExtensionFunction: + ResponseAction Run() override; +}; +#endif // BUILDFLAG(IS_CHROMEOS) + // Handles calls made via the chrome.power API. There is a separate instance of // this class for each profile, as requests are tracked by extension ID, but a // regular and incognito profile will share the same instance. diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h index 8795defad13431..87f8141adb9c9b 100644 --- a/extensions/browser/extension_function_histogram_value.h +++ b/extensions/browser/extension_function_histogram_value.h @@ -1833,6 +1833,7 @@ enum HistogramValue { WMDESKSPRIVATE_GETDESKBYID = 1770, AUTOFILLPRIVATE_ISVALIDIBAN = 1771, ACCESSIBILITY_PRIVATE_ISLACROSPRIMARY = 1772, + POWER_REPORTACTIVITY = 1773, // Last entry: Add new entries above, then run: // tools/metrics/histograms/update_extension_histograms.py ENUM_BOUNDARY diff --git a/extensions/common/api/_api_features.json b/extensions/common/api/_api_features.json index 54d9fe7ebe4224..cb65bb98862448 100644 --- a/extensions/common/api/_api_features.json +++ b/extensions/common/api/_api_features.json @@ -479,6 +479,9 @@ "dependencies": ["permission:power"], "contexts": ["blessed_extension"] }, + "power.reportActivity": { + "platforms": ["chromeos", "lacros"] + }, "printerProvider": { "dependencies": ["permission:printerProvider"], "contexts": ["blessed_extension"] diff --git a/extensions/common/api/_permission_features.json b/extensions/common/api/_permission_features.json index b318ef19cca207..e79c3deb43eb21 100644 --- a/extensions/common/api/_permission_features.json +++ b/extensions/common/api/_permission_features.json @@ -504,10 +504,15 @@ "min_manifest_version": 3, "feature_flag": "ExtensionsOffscreenDocuments" }, - "power": { + "power": [{ "channel": "stable", "extension_types": [ "extension", "legacy_packaged_app", "platform_app" ] }, + { + "channel": "stable", + "dependencies": ["behavior:imprivata_login_screen_extension"], + "extension_types": ["login_screen_extension"] + }], "printerProvider": { "channel": "stable", "extension_types": ["extension", "platform_app" ] diff --git a/extensions/common/api/power.idl b/extensions/common/api/power.idl index 7d5830004fb707..0e99fa8659ece0 100644 --- a/extensions/common/api/power.idl +++ b/extensions/common/api/power.idl @@ -5,6 +5,8 @@ // Use the chrome.power API to override the system's power // management features. namespace power { + callback VoidCallback = void (); + [noinline_doc] enum Level { // Prevent the system from sleeping in response to user inactivity. system, @@ -23,5 +25,11 @@ namespace power { // Releases a request previously made via requestKeepAwake(). static void releaseKeepAwake(); + + // Reports a user activity in order to awake the screen from a dimmed or + // turned off state or from a screensaver. Exits the screensaver if it is + // currently active. + [platforms=("chromeos", "lacros"), supportsPromises] + static void reportActivity(optional VoidCallback callback); }; }; diff --git a/extensions/common/features/feature_provider_unittest.cc b/extensions/common/features/feature_provider_unittest.cc index 4fd7b0a18f8dc0..ade733496b41a6 100644 --- a/extensions/common/features/feature_provider_unittest.cc +++ b/extensions/common/features/feature_provider_unittest.cc @@ -93,7 +93,7 @@ TEST(FeatureProviderTest, PermissionFeatureTypes) { // NOTE: This feature cannot have multiple rules, otherwise it is not a // SimpleFeature. const SimpleFeature* feature = static_cast( - FeatureProvider::GetPermissionFeature("power")); + FeatureProvider::GetPermissionFeature("alarms")); ASSERT_TRUE(feature); const std::vector& extension_types = feature->extension_types(); diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml index dca1fa031cf6d9..af0fc2f1a06ee1 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml @@ -36283,6 +36283,7 @@ Called by update_extension_histograms.py.--> +