Skip to content

Commit

Permalink
[VC] Tracking Linux app for VC.
Browse files Browse the repository at this point in the history
(1) A new VideoConferenceAshFeatureClient is added to track individual
    features that are VC related (accessing mic and camera)

(2) When VmCameraMicManager updates camera or mic capturing state, the
    notification is sent to VideoConferenceAshFeatureClient.

(3) The class is intentionally written close to
    VideoConferenceAppServiceClient so that there can be an easy
    refactoring if we ever need to.

(4) For CrostiniVm, PluginVm or Borealis, we can't know individual
    apps, so they are all treated as an individual app than a
    container of apps, this will be the case for sometime. And
    because of that, ReturnToApp is not implemented.


Bug=b:273820331
TEST="autoninja -C  out/Test chrome/test:browser_tests && out/Test/browser_tests --gtest_filter=VideoConferenceAshfeatureClientTest.*"

Change-Id: Ia41e8be0ad0cac40256b602dfa37b70af50fe7f9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4573953
Quick-Run: Charles Zhao <charleszhao@chromium.org>
Commit-Queue: Charles Zhao <charleszhao@chromium.org>
Reviewed-by: Andre Le <leandre@chromium.org>
Reviewed-by: Takashi Toyoshima <toyoshim@chromium.org>
Reviewed-by: Xiyuan Xia <xiyuan@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1153591}
  • Loading branch information
Guoxing Zhao authored and Chromium LUCI CQ committed Jun 6, 2023
1 parent 1d48e1e commit 7c615e3
Show file tree
Hide file tree
Showing 10 changed files with 690 additions and 1 deletion.
2 changes: 2 additions & 0 deletions chrome/browser/ash/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -3125,6 +3125,8 @@ source_set("ash") {
"usb/cros_usb_detector.h",
"video_conference/video_conference_app_service_client.cc",
"video_conference/video_conference_app_service_client.h",
"video_conference/video_conference_ash_feature_client.cc",
"video_conference/video_conference_ash_feature_client.h",
"vm_shutdown_observer.h",
"vm_starting_observer.h",
"wallpaper/wallpaper_drivefs_delegate_impl.cc",
Expand Down
14 changes: 13 additions & 1 deletion chrome/browser/ash/camera_mic/vm_camera_mic_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/ash/borealis/borealis_util.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_util.h"
#include "chrome/browser/ash/video_conference/video_conference_ash_feature_client.h"
#include "chrome/browser/notifications/notification_display_service.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/settings_window_manager_chromeos.h"
Expand Down Expand Up @@ -158,11 +159,22 @@ class VmCameraMicManager::VmInfo : public message_center::NotificationObserver {
int name_id() const { return name_id_; }
NotificationType notification_type() const { return notifications_.active; }

void SetMicActive(bool active) { OnDeviceUpdated(DeviceType::kMic, active); }
void SetMicActive(bool active) {
OnDeviceUpdated(DeviceType::kMic, active);

if (features::IsVideoConferenceEnabled()) {
VideoConferenceAshFeatureClient::Get()->OnVmDeviceUpdated(
vm_type_, DeviceType::kMic, active);
}
}

void SetCameraAccessing(bool accessing) {
camera_accessing_ = accessing;
OnCameraUpdated();
if (features::IsVideoConferenceEnabled()) {
VideoConferenceAshFeatureClient::Get()->OnVmDeviceUpdated(
vm_type_, DeviceType::kCamera, accessing);
}
}
void SetCameraPrivacyIsOn(bool on) {
camera_privacy_is_on_ = on;
Expand Down
4 changes: 4 additions & 0 deletions chrome/browser/ash/chrome_browser_main_parts_ash.cc
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@
#include "chrome/browser/ash/system_token_cert_db_initializer.h"
#include "chrome/browser/ash/usb/cros_usb_detector.h"
#include "chrome/browser/ash/video_conference/video_conference_app_service_client.h"
#include "chrome/browser/ash/video_conference/video_conference_ash_feature_client.h"
#include "chrome/browser/ash/wilco_dtc_supportd/wilco_dtc_supportd_manager.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part_ash.h"
Expand Down Expand Up @@ -1423,6 +1424,8 @@ void ChromeBrowserMainPartsAsh::PostBrowserStart() {
std::make_unique<video_conference::VideoConferenceManagerClientImpl>();
vc_app_service_client_ =
std::make_unique<VideoConferenceAppServiceClient>();
vc_ash_feature_client_ =
std::make_unique<VideoConferenceAshFeatureClient>();
}

apn_migrator_ = std::make_unique<ApnMigrator>(
Expand Down Expand Up @@ -1637,6 +1640,7 @@ void ChromeBrowserMainPartsAsh::PostMainMessageLoopRun() {

// vc_app_service_client_ has to be destructed before PostMainMessageLoopRun.
vc_app_service_client_.reset();
vc_ash_feature_client_.reset();

// Has a dependency on Profile, so it needs to be destroyed before Profile
// gets destroyed during ProfileManager destruction, which happens inside
Expand Down
2 changes: 2 additions & 0 deletions chrome/browser/ash/chrome_browser_main_parts_ash.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class ShutdownPolicyForwarder;
class SigninProfileHandler;
class SystemTokenCertDBInitializer;
class VideoConferenceAppServiceClient;
class VideoConferenceAshFeatureClient;
class WebKioskAppManager;
class KioskAppManager;

Expand Down Expand Up @@ -237,6 +238,7 @@ class ChromeBrowserMainPartsAsh : public ChromeBrowserMainPartsLinux {
lacros_data_backward_migration_mode_policy_observer_;

std::unique_ptr<VideoConferenceAppServiceClient> vc_app_service_client_;
std::unique_ptr<VideoConferenceAshFeatureClient> vc_ash_feature_client_;

std::unique_ptr<power::SmartChargingManager> smart_charging_manager_;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
// 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/ash/video_conference/video_conference_ash_feature_client.h"

#include "base/containers/contains.h"
#include "base/strings/utf_string_conversions.h"
#include "base/unguessable_token.h"
#include "chrome/browser/ash/borealis/borealis_prefs.h"
#include "chrome/browser/ash/crosapi/crosapi_ash.h"
#include "chrome/browser/ash/crosapi/crosapi_manager.h"
#include "chrome/browser/ash/crostini/crostini_pref_names.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_pref_names.h"
#include "chrome/browser/ash/video_conference/video_conference_manager_ash.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "components/prefs/pref_service.h"

namespace ash {
namespace {

VideoConferenceAshFeatureClient* g_client_instance = nullptr;

constexpr char kCrostiniVmId[] = "Linux";
constexpr char kPluginVmId[] = "PluginVm";
constexpr char kBorealisId[] = "Borealis";

// Returns an "Id" as an identifier for the VmType.
std::string ToVideoConferenceAppId(VmCameraMicManager::VmType vm_type) {
switch (vm_type) {
case VmCameraMicManager::VmType::kCrostiniVm:
return kCrostiniVmId;
case VmCameraMicManager::VmType::kPluginVm:
return kPluginVmId;
case VmCameraMicManager::VmType::kBorealis:
return kBorealisId;
}
}

} // namespace

VideoConferenceAshFeatureClient::VideoConferenceAshFeatureClient()
: client_id_(base::UnguessableToken::Create()),
status_(crosapi::mojom::VideoConferenceMediaUsageStatus::New(
/*client_id=*/client_id_,
/*has_media_app=*/false,
/*has_camera_permission=*/false,
/*has_microphone_permission=*/false,
/*is_capturing_camera=*/false,
/*is_capturing_microphone=*/false,
/*is_capturing_screen=*/false)) {
crosapi::CrosapiManager::Get()
->crosapi_ash()
->video_conference_manager_ash()
->RegisterCppClient(this, client_id_);

CHECK(!g_client_instance);
g_client_instance = this;
}

VideoConferenceAshFeatureClient::~VideoConferenceAshFeatureClient() {
// C++ clients are responsible for manually calling |UnregisterClient| on the
// manager when disconnecting.
crosapi::CrosapiManager::Get()
->crosapi_ash()
->video_conference_manager_ash()
->UnregisterClient(client_id_);

g_client_instance = nullptr;
}

void VideoConferenceAshFeatureClient::GetMediaApps(
GetMediaAppsCallback callback) {
std::vector<crosapi::mojom::VideoConferenceMediaAppInfoPtr> apps;

for (const auto& [app_id, app_state] : id_to_app_state_) {
const std::string app_name = GetAppName(app_id);

apps.push_back(crosapi::mojom::VideoConferenceMediaAppInfo::New(
/*id=*/app_state.token,
/*last_activity_time=*/app_state.last_activity_time,
/*is_capturing_camera=*/app_state.is_capturing_camera,
/*is_capturing_microphone=*/app_state.is_capturing_microphone,
/*is_capturing_screen=*/false,
/*title=*/base::UTF8ToUTF16(app_name),
/*url=*/absl::nullopt,
/*app_type=*/GetAppType(app_id)));
}

std::move(callback).Run(std::move(apps));
}

void VideoConferenceAshFeatureClient::ReturnToApp(
const base::UnguessableToken& token,
ReturnToAppCallback callback) {
// Currently, for Vms, we treat the whole VM as one app, so it is not clear
// which one to return to.
std::move(callback).Run(true);
}

void VideoConferenceAshFeatureClient::SetSystemMediaDeviceStatus(
crosapi::mojom::VideoConferenceMediaDevice device,
bool disabled,
SetSystemMediaDeviceStatusCallback callback) {
switch (device) {
case crosapi::mojom::VideoConferenceMediaDevice::kCamera:
camera_system_disabled_ = disabled;
std::move(callback).Run(true);
return;
case crosapi::mojom::VideoConferenceMediaDevice::kMicrophone:
microphone_system_disabled_ = disabled;
std::move(callback).Run(true);
return;
case crosapi::mojom::VideoConferenceMediaDevice::kUnusedDefault:
std::move(callback).Run(false);
return;
}
}

void VideoConferenceAshFeatureClient::OnVmDeviceUpdated(
VmCameraMicManager::VmType vm_type,
VmCameraMicManager::DeviceType device_type,
bool is_capturing) {
const AppIdString& app_id = ToVideoConferenceAppId(vm_type);

const bool is_already_tracked = base::Contains(id_to_app_state_, app_id);

// We only want to start tracking a app if it starts to accessing
// microphone/camera.
if (!is_already_tracked && !is_capturing) {
return;
}

AppState& state = GetOrAddAppState(app_id);
const std::string app_name = GetAppName(app_id);

if (device_type == VmCameraMicManager::DeviceType::kCamera) {
state.is_capturing_camera = is_capturing;
}

if (device_type == VmCameraMicManager::DeviceType::kMic) {
state.is_capturing_microphone = is_capturing;
}

MaybeRemoveApp(app_id);
HandleMediaUsageUpdate();

// This will be an AnchoredNudge, which is only visible if the tray is
// visible; so we have to call this after HandleMediaUsageUpdate.
if (device_type == VmCameraMicManager::DeviceType::kCamera && is_capturing &&
camera_system_disabled_) {
crosapi::CrosapiManager::Get()
->crosapi_ash()
->video_conference_manager_ash()
->NotifyDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kCamera,
base::UTF8ToUTF16(app_name), base::DoNothingAs<void(bool)>());
}

if (device_type == VmCameraMicManager::DeviceType::kMic && is_capturing &&
microphone_system_disabled_) {
crosapi::CrosapiManager::Get()
->crosapi_ash()
->video_conference_manager_ash()
->NotifyDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kMicrophone,
base::UTF8ToUTF16(app_name), base::DoNothingAs<void(bool)>());
}
}

// static
VideoConferenceAshFeatureClient* VideoConferenceAshFeatureClient::Get() {
return g_client_instance;
}

// For Ash Features, we simply keep the app_id and app_name as the same.
std::string VideoConferenceAshFeatureClient::GetAppName(
const AppIdString& app_id) {
return app_id;
}

// Get current camera/microphone permission of the `app_id`.
VideoConferenceAshFeatureClient::VideoConferencePermissions
VideoConferenceAshFeatureClient::GetAppPermission(const AppIdString& app_id) {
VideoConferencePermissions permissions{false, false};

// Get permission from prefs based in the app_id.
auto* prefs = ProfileManager::GetActiveUserProfile()->GetPrefs();
if (app_id == kCrostiniVmId) {
permissions.has_microphone_permission =
prefs->GetBoolean(crostini::prefs::kCrostiniMicAllowed);
}
if (app_id == kBorealisId) {
permissions.has_microphone_permission =
prefs->GetBoolean(borealis::prefs::kBorealisMicAllowed);
}
if (app_id == kPluginVmId) {
permissions.has_camera_permission =
prefs->GetBoolean(plugin_vm ::prefs::kPluginVmCameraAllowed);
permissions.has_microphone_permission =
prefs->GetBoolean(plugin_vm ::prefs::kPluginVmMicAllowed);
}
return permissions;
}

crosapi::mojom::VideoConferenceAppType
VideoConferenceAshFeatureClient::GetAppType(const AppIdString& app_id) {
if (app_id == kCrostiniVmId) {
return crosapi::mojom::VideoConferenceAppType::kCrostiniVm;
}

if (app_id == kPluginVmId) {
return crosapi::mojom::VideoConferenceAppType::kPluginVm;
}

if (app_id == kBorealisId) {
return crosapi::mojom::VideoConferenceAppType::kBorealis;
}

return crosapi::mojom::VideoConferenceAppType::kAshClientUnknown;
}

VideoConferenceAshFeatureClient::AppState&
VideoConferenceAshFeatureClient::GetOrAddAppState(const std::string& app_id) {
if (!base::Contains(id_to_app_state_, app_id)) {
id_to_app_state_[app_id] = AppState{base::UnguessableToken::Create(),
base::Time::Now(), false, false};
}
return id_to_app_state_[app_id];
}

void VideoConferenceAshFeatureClient::MaybeRemoveApp(
const AppIdString& app_id) {
if (!id_to_app_state_[app_id].is_capturing_microphone &&
!id_to_app_state_[app_id].is_capturing_camera) {
id_to_app_state_.erase(app_id);
}
}

void VideoConferenceAshFeatureClient::HandleMediaUsageUpdate() {
crosapi::mojom::VideoConferenceMediaUsageStatusPtr new_status =
crosapi::mojom::VideoConferenceMediaUsageStatus::New();
new_status->client_id = client_id_;
new_status->has_media_app = !id_to_app_state_.empty();

for (const auto& [app_id, app_state] : id_to_app_state_) {
new_status->is_capturing_camera |= app_state.is_capturing_camera;
new_status->is_capturing_microphone |= app_state.is_capturing_microphone;

VideoConferencePermissions permissions = GetAppPermission(app_id);
new_status->has_camera_permission |= permissions.has_camera_permission;
new_status->has_microphone_permission |=
permissions.has_microphone_permission;
}

// If `status` equals the previously sent status, don't notify manager.
if (new_status.Equals(status_)) {
return;
}
status_ = new_status->Clone();

auto callback = base::BindOnce([](bool success) {
if (!success) {
LOG(ERROR)
<< "VideoConferenceManager::NotifyMediaUsageUpdate did not succeed.";
}
});
crosapi::CrosapiManager::Get()
->crosapi_ash()
->video_conference_manager_ash()
->NotifyMediaUsageUpdate(std::move(new_status), std::move(callback));
}

} // namespace ash

0 comments on commit 7c615e3

Please sign in to comment.