From 982f0418f906685d794bc61263381182003abd94 Mon Sep 17 00:00:00 2001 From: Maria Petrisor Date: Fri, 17 Mar 2023 11:01:03 +0000 Subject: [PATCH] Add chrome.power.reportActivity() Adds a new method to chrome.power to report a user activity to ChromeOS. This is required in order to stop a commercial screensaver when the user performs actions not registered by ChromeOS but processed by commercial extensions. Currently badge taps are not processed by ChromeOS directly, but by the Imprivata extensions. In case a user taps their badge, the Imprivata extensions need to be able to notify ChromeOS of this and close the screensaver so that the user sees the second factor screen. Bug: 1402949 Change-Id: I7afac28749a2ed5461666a9f44b3f859c8417fb7 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4248626 Commit-Queue: Maria Petrisor Reviewed-by: Erik Chen Cr-Commit-Position: refs/heads/main@{#1118589} --- chrome/browser/ash/crosapi/BUILD.gn | 1 + chrome/browser/ash/crosapi/power_ash.cc | 5 + chrome/browser/ash/crosapi/power_ash.h | 1 + .../browser/ash/crosapi/power_ash_apitest.cc | 83 ++++++++++++ .../lacros/power_api_lacros_browsertest.cc | 124 ++++++++++++++++++ chrome/test/BUILD.gn | 2 + .../power/report_activity/manifest.json | 17 +++ .../api_test/power/report_activity/test.js | 19 +++ .../api_test/stubs_app/background.js | 9 +- chromeos/crosapi/mojom/power.mojom | 10 +- extensions/BUILD.gn | 1 + extensions/browser/api/power/BUILD.gn | 26 ++++ .../api/power/activity_reporter_delegate.cc | 30 +++++ .../api/power/activity_reporter_delegate.h | 24 ++++ .../power/activity_reporter_delegate_ash.cc | 21 +++ .../power/activity_reporter_delegate_ash.h | 27 ++++ .../activity_reporter_delegate_lacros.cc | 59 +++++++++ .../power/activity_reporter_delegate_lacros.h | 28 ++++ extensions/browser/api/power/power_api.cc | 12 ++ extensions/browser/api/power/power_api.h | 15 +++ .../extension_function_histogram_value.h | 1 + extensions/common/api/_api_features.json | 3 + .../common/api/_permission_features.json | 7 +- extensions/common/api/power.idl | 8 ++ .../features/feature_provider_unittest.cc | 2 +- tools/metrics/histograms/enums.xml | 1 + 26 files changed, 529 insertions(+), 7 deletions(-) create mode 100644 chrome/browser/ash/crosapi/power_ash_apitest.cc create mode 100644 chrome/browser/lacros/power_api_lacros_browsertest.cc create mode 100644 chrome/test/data/extensions/api_test/power/report_activity/manifest.json create mode 100644 chrome/test/data/extensions/api_test/power/report_activity/test.js create mode 100644 extensions/browser/api/power/activity_reporter_delegate.cc create mode 100644 extensions/browser/api/power/activity_reporter_delegate.h create mode 100644 extensions/browser/api/power/activity_reporter_delegate_ash.cc create mode 100644 extensions/browser/api/power/activity_reporter_delegate_ash.h create mode 100644 extensions/browser/api/power/activity_reporter_delegate_lacros.cc create mode 100644 extensions/browser/api/power/activity_reporter_delegate_lacros.h diff --git a/chrome/browser/ash/crosapi/BUILD.gn b/chrome/browser/ash/crosapi/BUILD.gn index 96c9044ed2841..59fff0c633bf5 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 d65240963db07..8e48bbcddd032 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 8eed5d150a2bf..3ff7a6606913e 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 0000000000000..966744b8fa03d --- /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 0000000000000..09e106a60da37 --- /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 2f8020d7eafee..d61de0cb916ae 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 0000000000000..f6011e3291be3 --- /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 0000000000000..2ef67f35a8757 --- /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 5463b3cb7b149..a4f7b6c866a4d 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 699dd33830c4e..3095f3aceb4f1 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 eb5b6dc41f187..dc28fd70a5a4c 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 0573516048a52..a96b72020ed14 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 0000000000000..453ec776fea40 --- /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 0000000000000..53d2370baa066 --- /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 0000000000000..5e3bd873d8f25 --- /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 0000000000000..4d8b7cbfae2ce --- /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 0000000000000..04acfb518b967 --- /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 0000000000000..c42b81cd91a8f --- /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 65b8fcb754622..4cde16ddbc35d 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 7c40da9c2ec4b..9a4c85a951261 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 8795defad1343..87f8141adb9c9 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 54d9fe7ebe422..cb65bb9886244 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 b318ef19cca20..e79c3deb43eb2 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 7d5830004fb70..0e99fa8659ece 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 4fd7b0a18f8dc..ade733496b41a 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 dca1fa031cf6d..af0fc2f1a06ee 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml @@ -36283,6 +36283,7 @@ Called by update_extension_histograms.py.--> +