| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/projector/projector_ui_controller.h" |
| |
| #include "ash/accessibility/caption_bubble_context_ash.h" |
| #include "ash/capture_mode/capture_mode_controller.h" |
| #include "ash/constants/ash_features.h" |
| #include "ash/constants/notifier_catalogs.h" |
| #include "ash/projector/projector_annotation_tray.h" |
| #include "ash/projector/projector_controller_impl.h" |
| #include "ash/projector/projector_metrics.h" |
| #include "ash/public/cpp/notification_utils.h" |
| #include "ash/public/cpp/projector/annotator_tool.h" |
| #include "ash/public/cpp/projector/projector_annotator_controller.h" |
| #include "ash/public/cpp/projector/projector_client.h" |
| #include "ash/public/cpp/system/toast_data.h" |
| #include "ash/public/cpp/window_properties.h" |
| #include "ash/resources/vector_icons/vector_icons.h" |
| #include "ash/root_window_controller.h" |
| #include "ash/shell.h" |
| #include "ash/system/toast/toast_manager_impl.h" |
| #include "base/functional/callback_helpers.h" |
| #include "components/live_caption/views/caption_bubble.h" |
| #include "components/live_caption/views/caption_bubble_model.h" |
| #include "ui/aura/window.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/gfx/vector_icon_types.h" |
| #include "ui/message_center/message_center.h" |
| #include "ui/message_center/public/cpp/notification.h" |
| #include "ui/message_center/public/cpp/notification_delegate.h" |
| #include "ui/message_center/public/cpp/notifier_id.h" |
| #include "ui/views/bubble/bubble_dialog_delegate_view.h" |
| #include "ui/views/widget/unique_widget_ptr.h" |
| #include "ui/views/widget/widget.h" |
| #include "url/gurl.h" |
| |
| namespace ash { |
| namespace { |
| |
| // A unique id to identify system notifications coming from this file. |
| constexpr char kProjectorNotifierId[] = "ash.projector_ui_controller"; |
| |
| // A unique id for system notifications reporting a generic failure. |
| constexpr char kProjectorErrorNotificationId[] = "projector_error_notification"; |
| |
| // A unique id for system notifications reporting a save failure. |
| constexpr char kProjectorSaveErrorNotificationId[] = |
| "projector_save_error_notification"; |
| |
| ProjectorAnnotationTray* GetProjectorAnnotationTrayForRoot(aura::Window* root) { |
| // It may happen that root is nullptr. This may happen in the event that |
| // the annotation tray is hidden before the canvas finishes its |
| // initialization. |
| if (!root) { |
| return nullptr; |
| } |
| |
| DCHECK(root->IsRootWindow()); |
| |
| // Recording can end when a display being fullscreen-captured gets removed, in |
| // this case, we don't need to hide the button. |
| if (root->is_destroying()) |
| return nullptr; |
| |
| // Can be null while shutting down. |
| auto* root_window_controller = RootWindowController::ForWindow(root); |
| if (!root_window_controller) |
| return nullptr; |
| |
| auto* projector_annotation_tray = |
| root_window_controller->GetStatusAreaWidget() |
| ->projector_annotation_tray(); |
| DCHECK(projector_annotation_tray); |
| return projector_annotation_tray; |
| } |
| |
| void SetProjectorAnnotationTrayVisibility(aura::Window* root, bool visible) { |
| if (auto* projector_annotation_tray = GetProjectorAnnotationTrayForRoot(root)) |
| projector_annotation_tray->SetVisiblePreferred(visible); |
| } |
| |
| void ToggleAnnotatorCanvas() { |
| auto* capture_mode_controller = CaptureModeController::Get(); |
| // TODO(b/200292852): This check should not be necessary, but because |
| // several Projector unit tests that rely on mocking and don't test the real |
| // code path, we can end up calling |ToggleRecordingOverlayEnabled()| |
| // without ever starting a Projector recording session. |
| // |CaptureModeController| asserts all invariants via DCHECKs, and those |
| // tests would crash. Remove any unnecessary mocks and test the real thing |
| // if possible. |
| if (capture_mode_controller->is_recording_in_progress()) { |
| capture_mode_controller->ToggleRecordingOverlayEnabled(); |
| } |
| } |
| |
| // Shows a Projector-related notification to the user with the given parameters. |
| void ShowNotification( |
| const std::string& notification_id, |
| int title_id, |
| int message_id, |
| message_center::SystemNotificationWarningLevel warning_level = |
| message_center::SystemNotificationWarningLevel::NORMAL, |
| const message_center::RichNotificationData& optional_fields = {}, |
| scoped_refptr<message_center::NotificationDelegate> delegate = nullptr, |
| const gfx::VectorIcon& notification_icon = kPaletteTrayIconProjectorIcon) { |
| std::unique_ptr<message_center::Notification> notification = |
| CreateSystemNotificationPtr( |
| message_center::NOTIFICATION_TYPE_SIMPLE, notification_id, |
| l10n_util::GetStringUTF16(title_id), |
| l10n_util::GetStringUTF16(message_id), |
| l10n_util::GetStringUTF16(IDS_ASH_PROJECTOR_DISPLAY_SOURCE), GURL(), |
| message_center::NotifierId( |
| message_center::NotifierType::SYSTEM_COMPONENT, |
| kProjectorNotifierId, NotificationCatalogName::kProjector), |
| optional_fields, delegate, notification_icon, warning_level); |
| |
| // Remove the previous notification before showing the new one if there are |
| // any. |
| auto* message_center = message_center::MessageCenter::Get(); |
| message_center->RemoveNotification(notification_id, |
| /*by_user=*/false); |
| message_center->AddNotification(std::move(notification)); |
| } |
| |
| } // namespace |
| |
| // static |
| void ProjectorUiController::ShowFailureNotification(int message_id, |
| int title_id) { |
| RecordCreationFlowError(message_id); |
| ShowNotification( |
| kProjectorErrorNotificationId, title_id, message_id, |
| message_center::SystemNotificationWarningLevel::CRITICAL_WARNING); |
| } |
| |
| // static |
| void ProjectorUiController::ShowSaveFailureNotification() { |
| RecordCreationFlowError(IDS_ASH_PROJECTOR_SAVE_FAILURE_TEXT); |
| ShowNotification( |
| kProjectorSaveErrorNotificationId, IDS_ASH_PROJECTOR_SAVE_FAILURE_TITLE, |
| IDS_ASH_PROJECTOR_SAVE_FAILURE_TEXT, |
| message_center::SystemNotificationWarningLevel::CRITICAL_WARNING); |
| } |
| |
| ProjectorUiController::ProjectorUiController( |
| ProjectorControllerImpl* projector_controller) { |
| projector_session_observation_.Observe( |
| projector_controller->projector_session()); |
| } |
| |
| ProjectorUiController::~ProjectorUiController() = default; |
| |
| void ProjectorUiController::ShowAnnotationTray(aura::Window* current_root) { |
| current_root_ = current_root; |
| |
| // Show the tray icon. |
| SetProjectorAnnotationTrayVisibility(current_root_, /*visible=*/true); |
| } |
| |
| void ProjectorUiController::HideAnnotationTray() { |
| ResetTools(); |
| // Hide the tray icon. |
| if (auto* projector_annotation_tray = |
| GetProjectorAnnotationTrayForRoot(current_root_)) { |
| projector_annotation_tray->HideAnnotationTray(); |
| } |
| |
| canvas_initialized_state_.reset(); |
| current_root_ = nullptr; |
| } |
| |
| void ProjectorUiController::EnableAnnotatorTool() { |
| if (!annotator_enabled_) { |
| ToggleAnnotatorCanvas(); |
| annotator_enabled_ = !annotator_enabled_; |
| RecordToolbarMetrics(ProjectorToolbar::kMarkerTool); |
| } |
| } |
| |
| void ProjectorUiController::SetAnnotatorTool(const AnnotatorTool& tool) { |
| ash::ProjectorAnnotatorController::Get()->SetTool(tool); |
| RecordMarkerColorMetrics(GetMarkerColorForMetrics(tool.color)); |
| } |
| |
| void ProjectorUiController::ResetTools() { |
| if (annotator_enabled_) { |
| ToggleAnnotatorCanvas(); |
| annotator_enabled_ = false; |
| ash::ProjectorAnnotatorController::Get()->Clear(); |
| } |
| } |
| |
| void ProjectorUiController::OnCanvasInitialized(bool success) { |
| canvas_initialized_state_ = success; |
| UpdateTrayEnabledState(); |
| } |
| |
| bool ProjectorUiController::GetAnnotatorAvailability() { |
| if (!canvas_initialized_state_) { |
| return false; |
| } |
| return *canvas_initialized_state_; |
| } |
| |
| void ProjectorUiController::ToggleAnnotationTray() { |
| if (auto* projector_annotation_tray = |
| GetProjectorAnnotationTrayForRoot(current_root_)) { |
| projector_annotation_tray->ToggleAnnotator(); |
| } |
| } |
| |
| void ProjectorUiController::OnRecordedWindowChangingRoot( |
| aura::Window* new_root) { |
| DCHECK_NE(new_root, current_root_); |
| |
| SetProjectorAnnotationTrayVisibility(current_root_, /*visible=*/false); |
| SetProjectorAnnotationTrayVisibility(new_root, /*visible=*/true); |
| current_root_ = new_root; |
| if (GetAnnotatorAvailability()) |
| UpdateTrayEnabledState(); |
| } |
| |
| void ProjectorUiController::OnProjectorSessionActiveStateChanged(bool active) { |
| if (!active) |
| ResetTools(); |
| } |
| |
| ProjectorMarkerColor ProjectorUiController::GetMarkerColorForMetrics( |
| SkColor color) { |
| std::map<SkColor, ProjectorMarkerColor> marker_colors_map = { |
| {kProjectorMagentaPenColor, ProjectorMarkerColor::kMagenta}, |
| {kProjectorBluePenColor, ProjectorMarkerColor::kBlue}, |
| {kProjectorRedPenColor, ProjectorMarkerColor::kRed}, |
| {kProjectorYellowPenColor, ProjectorMarkerColor::kYellow}}; |
| return marker_colors_map[color]; |
| } |
| |
| void ProjectorUiController::UpdateTrayEnabledState() { |
| if (auto* projector_annotation_tray = |
| GetProjectorAnnotationTrayForRoot(current_root_)) { |
| projector_annotation_tray->SetTrayEnabled(GetAnnotatorAvailability()); |
| } |
| } |
| } // namespace ash |