Skip to content

Commit

Permalink
Add chrome.power.reportActivity()
Browse files Browse the repository at this point in the history
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 <mpetrisor@chromium.org>
Reviewed-by: Erik Chen <erikchen@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1118589}
  • Loading branch information
Maria Petrisor authored and Chromium LUCI CQ committed Mar 17, 2023
1 parent efefe2c commit 982f041
Show file tree
Hide file tree
Showing 26 changed files with 529 additions and 7 deletions.
1 change: 1 addition & 0 deletions chrome/browser/ash/crosapi/BUILD.gn
Expand Up @@ -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",
Expand Down
5 changes: 5 additions & 0 deletions chrome/browser/ash/crosapi/power_ash.cc
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
}
Expand Down
1 change: 1 addition & 0 deletions chrome/browser/ash/crosapi/power_ash.h
Expand Up @@ -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(),
Expand Down
83 changes: 83 additions & 0 deletions 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<ContextType> {
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
124 changes: 124 additions & 0 deletions 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<crosapi::mojom::Power>()) {
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<crosapi::mojom::IdleService>()) {
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<crosapi::mojom::IdleService>()
->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<crosapi::mojom::IdleInfoObserver> 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<crosapi::mojom::Power>()
.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
2 changes: 2 additions & 0 deletions chrome/test/BUILD.gn
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
@@ -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"
]
}
19 changes: 19 additions & 0 deletions 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');
9 changes: 5 additions & 4 deletions chrome/test/data/extensions/api_test/stubs_app/background.js
Expand Up @@ -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);
}
});
Expand Down
10 changes: 9 additions & 1 deletion chromeos/crosapi/mojom/power.mojom
Expand Up @@ -12,12 +12,20 @@ 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.
AddPowerSaveBlocker@0(pending_remote<PowerWakeLock> lock,
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();
};
1 change: 1 addition & 0 deletions extensions/BUILD.gn
Expand Up @@ -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",
Expand Down
26 changes: 26 additions & 0 deletions extensions/browser/api/power/BUILD.gn
Expand Up @@ -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,
Expand All @@ -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" ]
}

0 comments on commit 982f041

Please sign in to comment.