Resolved the issue to monitor lock/unlock in platform SDK

PiperOrigin-RevId: 552655345
This commit is contained in:
Guogang Li
2023-07-31 19:13:27 -07:00
committed by Copybara-Service
parent 9d2cfc19c9
commit 868d3ce046
6 changed files with 323 additions and 212 deletions
@@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
licenses(["notice"])
cc_library(
@@ -95,6 +96,7 @@ cc_library(
"mutex.h",
"scheduled_executor.h",
"server_sync.h",
"session_manager.h",
"submittable_executor.h",
"thread_pool.h",
"webrtc.h",
@@ -166,6 +168,7 @@ cc_library(
"preferences_repository.cc",
"preferences_repository.h",
"scheduled_executor.cc",
"session_manager.cc",
"submittable_executor.cc",
"system_clock.cc",
"thread_pool.cc",
@@ -23,11 +23,14 @@
#include <functional>
#include <optional>
#include <string>
#include <utility>
#include "absl/container/flat_hash_map.h"
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
#include "internal/base/bluetooth_address.h"
#include "internal/platform/implementation/device_info.h"
#include "internal/platform/implementation/windows/session_manager.h"
#include "internal/platform/logging.h"
#include "winrt/Windows.Foundation.Collections.h"
#include "winrt/Windows.Foundation.h"
@@ -49,89 +52,10 @@ using IVectorView = winrt::Windows::Foundation::Collections::IVectorView<T>;
template <typename T>
using IAsyncOperation = winrt::Windows::Foundation::IAsyncOperation<T>;
constexpr char window_class_name[] = "NearbySharingDLL_MessageWindowClass";
constexpr char window_name[] = "NearbySharingDLL_MessageWindow";
constexpr char logs_relative_path[] = "Google\\Nearby\\Sharing\\Logs";
constexpr char crash_dumps_relative_path[] =
"Google\\Nearby\\Sharing\\CrashDumps";
namespace {
// This WindowProc method must be static for the successful initialization of
// WNDCLASS
// window_class.lpfnWndProc = (WNDPROC) &DeviceInfo::WindowProc;
// where a WNDPROC typed function pointer is expected
// typedef LRESULT (CALLBACK* WNDPROC)(HWND,UINT,WPARAM,LPARAM)
// the calling convention used here CALLBACK is a macro defined as
// #define CALLBACK __stdcall
//
// If WindProc is not static and defined as a member function, it uses the
// __thiscall calling convention instead
// https://docs.microsoft.com/en-us/cpp/cpp/thiscall?view=msvc-170
// https://isocpp.org/wiki/faq/pointers-to-members
// https://en.cppreference.com/w/cpp/language/pointer
//
// This is problematic because the function pointer now looks like this
// typedef LRESULT (CALLBACK* DeviceInfo_WNDPROC)(DeviceInfo*
// this,HWND,UINT,WPARAM,LPARAM)
// which causes casting errors
LRESULT CALLBACK WindowProc(HWND window_handle, UINT message, WPARAM wparam,
LPARAM lparam) {
DeviceInfo* self = reinterpret_cast<DeviceInfo*>(
GetWindowLongPtr(window_handle, GWLP_USERDATA));
CREATESTRUCT* create_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
LONG_PTR result = 0L;
switch (message) {
case WM_CREATE:
self = reinterpret_cast<DeviceInfo*>(create_struct->lpCreateParams);
self->message_window_handle_ = window_handle;
// Store pointer to the self to the window's user data.
SetLastError(0);
result = SetWindowLongPtr(window_handle, GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(self));
if (result == 0 && GetLastError() != 0) {
NEARBY_LOGS(ERROR)
<< __func__
<< ": Error connecting message window to Nearby Sharing DLL.";
}
break;
case WM_WTSSESSION_CHANGE:
if (self) {
switch (wparam) {
case WTS_SESSION_LOCK:
for (auto& listener : self->screen_locked_listeners_) {
listener.second(api::DeviceInfo::ScreenStatus::
kLocked); // Trigger registered callbacks
}
break;
case WTS_SESSION_UNLOCK:
for (auto& listener : self->screen_locked_listeners_) {
listener.second(api::DeviceInfo::ScreenStatus::
kUnlocked); // Trigger registered callbacks
}
break;
}
}
break;
case WM_DESTROY:
SetLastError(0);
result = SetWindowLongPtr(window_handle, GWLP_USERDATA, NULL);
if (result == 0 && GetLastError() != 0) {
NEARBY_LOGS(ERROR)
<< __func__
<< ": Error disconnecting message window to Nearby Sharing DLL.";
}
break;
}
return DefWindowProc(window_handle, message, wparam, lparam);
}
} // namespace
DeviceInfo::~DeviceInfo() {
UnregisterClass(MAKEINTATOM(registered_class_), instance_);
}
std::optional<std::u16string> DeviceInfo::GetOsDeviceName() const {
DWORD size = 0;
@@ -404,78 +328,29 @@ std::optional<std::filesystem::path> DeviceInfo::GetCrashDumpPath() const {
}
bool DeviceInfo::IsScreenLocked() const {
DWORD session_id = WTSGetActiveConsoleSessionId();
WTS_INFO_CLASS wts_info_class = WTSSessionInfoEx;
LPTSTR session_info_buffer = nullptr;
DWORD session_info_buffer_size_bytes = 0;
WTSINFOEXW* wts_info = nullptr;
LONG session_state = WTS_SESSIONSTATE_UNKNOWN;
if (WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, session_id,
wts_info_class, &session_info_buffer,
&session_info_buffer_size_bytes)) {
if (session_info_buffer_size_bytes > 0) {
wts_info = (WTSINFOEXW*)session_info_buffer;
if (wts_info->Level == 1) {
session_state = wts_info->Data.WTSInfoExLevel1.SessionFlags;
}
}
WTSFreeMemory(session_info_buffer);
session_info_buffer = nullptr;
}
return (session_state == WTS_SESSIONSTATE_LOCK);
absl::MutexLock lock(&mutex_);
return session_manager_.IsScreenLocked();
}
void DeviceInfo::RegisterScreenLockedListener(
absl::string_view listener_name,
std::function<void(api::DeviceInfo::ScreenStatus)> callback) {
if (message_window_handle_ == nullptr) {
instance_ = (HINSTANCE)GetModuleHandle(NULL);
WNDCLASS window_class;
window_class.style = 0;
window_class.lpfnWndProc = (WNDPROC)&WindowProc;
window_class.cbClsExtra = 0;
window_class.cbWndExtra = 0;
window_class.hInstance = instance_;
window_class.hIcon = nullptr;
window_class.hCursor = nullptr;
window_class.hbrBackground = nullptr;
window_class.lpszMenuName = nullptr;
window_class.lpszClassName = window_class_name;
registered_class_ = RegisterClass(&window_class);
message_window_handle_ = CreateWindow(
MAKEINTATOM(registered_class_), // class atom from RegisterClass
window_name, // window name
0, // window style
0, // initial x position of window
0, // initial y position of window
0, // width
0, // height
HWND_MESSAGE, // handle to the parent of window
// (message-only window in this case)
nullptr, // handle to a menu
instance_, // handle to the instance of the module
// associated to the window
this); // pointer to be passed to the window for additional data
if (!message_window_handle_) {
NEARBY_LOGS(ERROR)
<< __func__
<< ": Failed to create message window for Nearby Sharing DLL.";
}
}
screen_locked_listeners_.emplace(listener_name, callback);
absl::MutexLock lock(&mutex_);
session_manager_.RegisterSessionListener(
listener_name,
[callback = std::move(callback)](SessionManager::SessionState state) {
if (state == SessionManager::SessionState::kLock) {
callback(api::DeviceInfo::ScreenStatus::kLocked);
} else if (state == SessionManager::SessionState::kUnlock) {
callback(api::DeviceInfo::ScreenStatus::kUnlocked);
}
});
}
void DeviceInfo::UnregisterScreenLockedListener(
absl::string_view listener_name) {
screen_locked_listeners_.erase(listener_name);
absl::MutexLock lock(&mutex_);
session_manager_.UnregisterSessionListener(listener_name);
}
} // namespace windows
@@ -1,4 +1,4 @@
// Copyright 2021 Google LLC
// Copyright 2021-2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,25 +15,21 @@
#ifndef PLATFORM_IMPL_WINDOWS_DEVICE_INFO_H_
#define PLATFORM_IMPL_WINDOWS_DEVICE_INFO_H_
#include <guiddef.h>
#include <windows.h>
#include <array>
#include <functional>
#include <optional>
#include <string>
#include "absl/container/flat_hash_map.h"
#include "absl/base/thread_annotations.h"
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
#include "internal/platform/implementation/device_info.h"
#include "winrt/Windows.Foundation.h"
#include "internal/platform/implementation/windows/session_manager.h"
namespace nearby {
namespace windows {
class DeviceInfo : public api::DeviceInfo {
public:
~DeviceInfo() override;
~DeviceInfo() override = default;
std::optional<std::u16string> GetOsDeviceName() const override;
api::DeviceInfo::DeviceType GetDeviceType() const override;
@@ -55,13 +51,10 @@ class DeviceInfo : public api::DeviceInfo {
absl::string_view listener_name,
std::function<void(api::DeviceInfo::ScreenStatus)> callback) override;
void UnregisterScreenLockedListener(absl::string_view listener_name) override;
absl::flat_hash_map<std::string,
std::function<void(api::DeviceInfo::ScreenStatus)>>
screen_locked_listeners_;
HINSTANCE instance_ = nullptr;
ATOM registered_class_ = NULL;
HWND message_window_handle_ = nullptr;
private:
mutable absl::Mutex mutex_;
SessionManager session_manager_ ABSL_GUARDED_BY(mutex_);
};
} // namespace windows
@@ -77,61 +77,6 @@ TEST(DeviceInfo, DISABLED_IsScreenLocked) {
EXPECT_FALSE(DeviceInfo().IsScreenLocked());
}
TEST(DeviceInfo, DISABLED_RegisterScreenLockedListener) {
std::function<void(api::DeviceInfo::ScreenStatus)> listener_1 =
[](api::DeviceInfo::ScreenStatus) {};
std::function<void(api::DeviceInfo::ScreenStatus)> listener_2 =
[](api::DeviceInfo::ScreenStatus) {};
DeviceInfo device_info;
EXPECT_EQ(device_info.screen_locked_listeners_.size(), 0);
device_info.RegisterScreenLockedListener("listener_1", listener_1);
EXPECT_EQ(device_info.screen_locked_listeners_.size(), 1);
device_info.RegisterScreenLockedListener("listener_2", listener_2);
EXPECT_EQ(device_info.screen_locked_listeners_.size(), 2);
}
TEST(DeviceInfo, DISABLED_UnregisterScreenLockedListener) {
std::function<void(api::DeviceInfo::ScreenStatus)> listener_1 =
[](api::DeviceInfo::ScreenStatus) {};
std::function<void(api::DeviceInfo::ScreenStatus)> listener_2 =
[](api::DeviceInfo::ScreenStatus) {};
DeviceInfo device_info;
EXPECT_EQ(device_info.screen_locked_listeners_.size(), 0);
device_info.RegisterScreenLockedListener("listener_1", listener_1);
device_info.RegisterScreenLockedListener("listener_2", listener_2);
EXPECT_EQ(device_info.screen_locked_listeners_.size(), 2);
device_info.UnregisterScreenLockedListener("listener_1");
EXPECT_EQ(device_info.screen_locked_listeners_.size(), 1);
device_info.UnregisterScreenLockedListener("listener_2");
EXPECT_EQ(device_info.screen_locked_listeners_.size(), 0);
}
TEST(DeviceInfo, DISABLED_UpdateScreenLockedListener) {
absl::Notification notification;
api::DeviceInfo::ScreenStatus screen_locked_tracker =
api::DeviceInfo::ScreenStatus::kUndetermined;
std::function<void(api::DeviceInfo::ScreenStatus)> listener =
[&screen_locked_tracker,
&notification](api::DeviceInfo::ScreenStatus status) {
screen_locked_tracker = api::DeviceInfo::ScreenStatus::kLocked;
notification.Notify();
};
DeviceInfo device_info;
device_info.RegisterScreenLockedListener("listener", listener);
EXPECT_TRUE(notification.WaitForNotificationWithTimeout(absl::Seconds(5)));
EXPECT_EQ(screen_locked_tracker, api::DeviceInfo::ScreenStatus::kLocked);
}
} // namespace
} // namespace windows
} // namespace nearby
@@ -0,0 +1,238 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/windows/session_manager.h"
#include <Windows.h>
#include <wtsapi32.h>
#include <string>
#include "absl/base/attributes.h"
#include "absl/container/flat_hash_map.h"
#include "absl/functional/any_invocable.h"
#include "absl/synchronization/mutex.h"
#include "absl/synchronization/notification.h"
#include "internal/platform/implementation/windows/submittable_executor.h"
#include "internal/platform/logging.h"
namespace nearby {
namespace windows {
namespace {
constexpr char kMessageWindowClass[] = "Nearby Message Window Class";
constexpr char kMessageWindowTitle[] = "Nearby Message Dummy Window";
// Define global static variables.
ABSL_CONST_INIT absl::Mutex kSessionMutex(absl::kConstInit);
HWND kSessionHwnd = nullptr;
SubmittableExecutor* kSessionThread = nullptr;
absl::flat_hash_map<std::string,
absl::AnyInvocable<void(SessionManager::SessionState)>>*
kSessionCallbacks = nullptr;
LRESULT CALLBACK NearbyWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
LPARAM lParam) {
switch (uMsg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_WTSSESSION_CHANGE:
if (wParam == WTS_SESSION_LOCK) {
absl::MutexLock lock(&kSessionMutex);
for (auto& it : *kSessionCallbacks) {
it.second(SessionManager::SessionState::kLock);
}
} else if (wParam == WTS_SESSION_UNLOCK) {
absl::MutexLock lock(&kSessionMutex);
for (auto& it : *kSessionCallbacks) {
it.second(SessionManager::SessionState::kUnlock);
}
}
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
HWND CreateNearbyWindow() {
HINSTANCE instance = (HINSTANCE)GetModuleHandle(nullptr);
WNDCLASS window_class = {};
window_class.lpfnWndProc = NearbyWindowProc;
window_class.hInstance = instance;
window_class.lpszClassName = kMessageWindowClass;
RegisterClass(&window_class);
HWND hwnd =
CreateWindowA(kMessageWindowClass, kMessageWindowTitle, /*dwStyle=*/0,
/*X=*/0, /*Y=*/0, /*nWidth=*/0, /*nHeight=*/0, HWND_MESSAGE,
/*hMenu=*/nullptr, instance,
/*lpParam=*/nullptr);
return hwnd;
}
} // namespace
SessionManager::~SessionManager() { StopSession(); }
bool SessionManager::RegisterSessionListener(
absl::string_view listener_name,
absl::AnyInvocable<void(SessionState)> callback) {
absl::MutexLock lock(&kSessionMutex);
// Create session thread if no running thread.
if (kSessionThread == nullptr) {
absl::Notification notification;
kSessionThread = new SubmittableExecutor();
kSessionCallbacks = new absl::flat_hash_map<
std::string, absl::AnyInvocable<void(SessionManager::SessionState)>>();
kSessionThread->Execute(
[this, &notification]() { StartSession(notification); });
notification.WaitForNotification();
if (kSessionThread == nullptr) {
return false;
}
}
if (kSessionCallbacks->contains(listener_name) ||
listeners_.contains(listener_name)) {
return false;
}
kSessionCallbacks->emplace(listener_name, std::move(callback));
listeners_.emplace(listener_name);
return true;
}
bool SessionManager::UnregisterSessionListener(
absl::string_view listener_name) {
absl::MutexLock lock(&kSessionMutex);
if (kSessionThread == nullptr) {
NEARBY_LOGS(ERROR) << __func__ << ": No running listener.";
return false;
}
if (!kSessionCallbacks->contains(listener_name) ||
!listeners_.contains(listener_name)) {
NEARBY_LOGS(ERROR) << __func__
<< ": No listener with name:" << listener_name;
return false;
}
kSessionCallbacks->erase(listener_name);
listeners_.erase(listener_name);
if (!kSessionCallbacks->empty()) {
return true;
}
CleanUp();
return true;
}
bool SessionManager::IsScreenLocked() const {
DWORD session_id = WTSGetActiveConsoleSessionId();
WTS_INFO_CLASS wts_info_class = WTSSessionInfoEx;
LPTSTR session_info_buffer = nullptr;
DWORD session_info_buffer_size_bytes = 0;
WTSINFOEXW* wts_info = nullptr;
LONG session_state = WTS_SESSIONSTATE_UNKNOWN;
if (WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, session_id,
wts_info_class, &session_info_buffer,
&session_info_buffer_size_bytes)) {
if (session_info_buffer_size_bytes > 0) {
wts_info = (WTSINFOEXW*)session_info_buffer;
if (wts_info->Level == 1) {
session_state = wts_info->Data.WTSInfoExLevel1.SessionFlags;
}
}
WTSFreeMemory(session_info_buffer);
session_info_buffer = nullptr;
}
return (session_state == WTS_SESSIONSTATE_LOCK);
}
void SessionManager::StartSession(absl::Notification& notification) {
kSessionHwnd = CreateNearbyWindow();
if (kSessionHwnd == nullptr) {
NEARBY_LOGS(ERROR) << __func__ << ": Failed to create session Window.";
return;
}
if (!WTSRegisterSessionNotification(kSessionHwnd, NOTIFY_FOR_THIS_SESSION)) {
NEARBY_LOGS(ERROR) << __func__
<< ":Failed to register session notification.";
return;
}
notification.Notify();
// Main message loop
MSG msg = {};
while (GetMessage(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (!WTSUnRegisterSessionNotification(kSessionHwnd)) {
NEARBY_LOGS(ERROR) << __func__
<< ": Failed to register session notification.";
return;
}
if (!UnregisterClassA(/*lpClassName=*/kMessageWindowClass,
/*hInstance=*/(HINSTANCE)GetModuleHandle(nullptr))) {
NEARBY_LOGS(ERROR) << __func__ << ": Failed to unregister window class.";
}
NEARBY_LOGS(INFO) << __func__ << ": Completed Message loop.";
}
void SessionManager::StopSession() {
absl::MutexLock lock(&kSessionMutex);
if (kSessionThread == nullptr) {
return;
}
for (const auto& it : listeners_) {
kSessionCallbacks->erase(it);
}
listeners_.clear();
if (!kSessionCallbacks->empty()) {
return;
}
CleanUp();
}
void SessionManager::CleanUp() {
if (kSessionHwnd != nullptr) {
// Send message to destroy message window.
PostMessageA(kSessionHwnd, WM_DESTROY, 0, 0);
}
kSessionThread->Shutdown();
delete kSessionThread;
delete kSessionCallbacks;
kSessionThread = nullptr;
kSessionCallbacks = nullptr;
kSessionHwnd = nullptr;
}
} // namespace windows
} // namespace nearby
@@ -0,0 +1,57 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_SESSION_MANAGER_H_
#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_SESSION_MANAGER_H_
#include <string>
#include "absl/container/flat_hash_set.h"
#include "absl/functional/any_invocable.h"
#include "absl/strings/string_view.h"
#include "absl/synchronization/notification.h"
namespace nearby {
namespace windows {
// SessionManager provides methods to access/control platform session.
class SessionManager {
public:
enum class SessionState { kLock, kUnlock };
~SessionManager();
// Setups session listener.
// listener_name - Listener name. it should be unique in the SDK level.
// callback - It will be called when session state changed, such as
// lock/unlock screen.
bool RegisterSessionListener(absl::string_view listener_name,
absl::AnyInvocable<void(SessionState)> callback);
// Removes session listener by its name.
bool UnregisterSessionListener(absl::string_view listener_name);
bool IsScreenLocked() const;
private:
void StartSession(absl::Notification& notification);
void StopSession();
void CleanUp();
absl::flat_hash_set<std::string> listeners_;
};
} // namespace windows
} // namespace nearby
#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_SESSION_MANAGER_H_