| // Copyright 2013 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/accelerators/debug_commands.h" |
| |
| #include <string> |
| #include <utility> |
| |
| #include "ash/accelerators/accelerator_commands.h" |
| #include "ash/capture_mode/capture_mode_controller.h" |
| #include "ash/constants/ash_features.h" |
| #include "ash/constants/ash_pref_names.h" |
| #include "ash/constants/ash_switches.h" |
| #include "ash/constants/notifier_catalogs.h" |
| #include "ash/hud_display/hud_display.h" |
| #include "ash/public/cpp/accelerators.h" |
| #include "ash/public/cpp/capture_mode/capture_mode_api.h" |
| #include "ash/public/cpp/debug_utils.h" |
| #include "ash/public/cpp/system/toast_data.h" |
| #include "ash/resources/vector_icons/vector_icons.h" |
| #include "ash/root_window_controller.h" |
| #include "ash/scanner/scanner_metrics.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shell.h" |
| #include "ash/style/ash_color_id.h" |
| #include "ash/style/color_palette_controller.h" |
| #include "ash/style/dark_light_mode_controller_impl.h" |
| #include "ash/style/mojom/color_scheme.mojom-shared.h" |
| #include "ash/style/style_viewer/system_ui_components_style_viewer_view.h" |
| #include "ash/system/focus_mode/focus_mode_controller.h" |
| #include "ash/system/power/power_button_controller.h" |
| #include "ash/system/status_area_widget.h" |
| #include "ash/system/toast/anchored_nudge_manager_impl.h" |
| #include "ash/system/toast/toast_manager_impl.h" |
| #include "ash/system/video_conference/video_conference_common.h" |
| #include "ash/system/video_conference/video_conference_tray.h" |
| #include "ash/system/video_conference/video_conference_tray_controller.h" |
| #include "ash/touch/touch_devices_controller.h" |
| #include "ash/virtual_trackpad/virtual_trackpad_view.h" |
| #include "ash/wallpaper/wallpaper_controller_impl.h" |
| #include "ash/wm/float/float_controller.h" |
| #include "ash/wm/tablet_mode/tablet_mode_controller.h" |
| #include "ash/wm/window_restore/informed_restore_controller.h" |
| #include "ash/wm/window_state.h" |
| #include "ash/wm/window_util.h" |
| #include "base/command_line.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/metrics/user_metrics_action.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chromeos/constants/chromeos_features.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/vector_icons/vector_icons.h" |
| #include "ui/accessibility/ax_tree_id.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/display/manager/display_manager.h" |
| #include "ui/display/screen.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/geometry/skia_conversions.h" |
| #include "ui/gfx/image/image_skia.h" |
| #include "ui/gfx/image/image_skia_rep.h" |
| #include "ui/native_theme/native_theme.h" |
| #include "ui/views/widget/widget.h" |
| |
| #if !BUILDFLAG(IS_CHROMEOS_DEVICE) |
| #include "ash/examples/test_app_window.h" |
| #endif |
| |
| namespace ash { |
| namespace debug { |
| namespace { |
| |
| void HandlePrintLayerHierarchy() { |
| std::ostringstream out; |
| PrintLayerHierarchy(&out); |
| LOG(ERROR) << out.str(); |
| } |
| |
| void HandlePrintViewHierarchy() { |
| std::ostringstream out; |
| PrintViewHierarchy(&out); |
| LOG(ERROR) << out.str(); |
| } |
| |
| void HandlePrintWindowHierarchy() { |
| std::ostringstream out; |
| PrintWindowHierarchy(&out, /*scrub_data=*/false); |
| LOG(ERROR) << out.str(); |
| } |
| |
| gfx::ImageSkia CreateWallpaperImage(SkColor fill, SkColor rect) { |
| // TODO(oshima): Consider adding a command line option to control wallpaper |
| // images for testing. The size is randomly picked. |
| gfx::Size image_size(1366, 768); |
| SkBitmap bitmap; |
| bitmap.allocN32Pixels(image_size.width(), image_size.height(), true); |
| SkCanvas canvas(bitmap); |
| canvas.drawColor(fill); |
| SkPaint paint; |
| paint.setColor(rect); |
| paint.setStrokeWidth(10); |
| paint.setStyle(SkPaint::kStroke_Style); |
| paint.setBlendMode(SkBlendMode::kSrcOver); |
| canvas.drawRoundRect(gfx::RectToSkRect(gfx::Rect(image_size)), 100.f, 100.f, |
| paint); |
| return gfx::ImageSkia::CreateFromBitmap(std::move(bitmap), 1.f); |
| } |
| |
| void HandleToggleWallpaperMode() { |
| static int index = 0; |
| auto* wallpaper_controller = Shell::Get()->wallpaper_controller(); |
| WallpaperInfo info("", WALLPAPER_LAYOUT_STRETCH, WallpaperType::kDefault, |
| base::Time::Now().LocalMidnight()); |
| switch (++index % 4) { |
| case 0: |
| wallpaper_controller->ShowDefaultWallpaperForTesting(); |
| break; |
| case 1: |
| wallpaper_controller->ShowWallpaperImage( |
| CreateWallpaperImage(SK_ColorRED, SK_ColorBLUE), info, |
| /*preview_mode=*/false, /*always_on_top=*/false); |
| break; |
| case 2: |
| info.layout = WALLPAPER_LAYOUT_CENTER; |
| wallpaper_controller->ShowWallpaperImage( |
| CreateWallpaperImage(SK_ColorBLUE, SK_ColorGREEN), info, |
| /*preview_mode=*/false, /*always_on_top=*/false); |
| break; |
| case 3: |
| info.layout = WALLPAPER_LAYOUT_CENTER_CROPPED; |
| wallpaper_controller->ShowWallpaperImage( |
| CreateWallpaperImage(SK_ColorGREEN, SK_ColorRED), info, |
| /*preview_mode=*/false, /*always_on_top=*/false); |
| break; |
| } |
| } |
| |
| void HandleToggleDarkMode() { |
| // Toggling dark mode requires that the active user session has started |
| // since the feature is backed by user preferences. |
| if (auto* controller = Shell::Get()->session_controller(); |
| !(controller && controller->IsActiveUserSessionStarted())) { |
| return; |
| } |
| |
| if (auto* controller = DarkLightModeControllerImpl::Get()) |
| controller->ToggleColorMode(); |
| } |
| |
| void HandleToggleDynamicColor() { |
| static int index = 0; |
| SkColor color; |
| switch (++index % 2) { |
| case 0: |
| color = SK_ColorGREEN; |
| break; |
| case 1: |
| color = SK_ColorRED; |
| break; |
| } |
| |
| // This behavior is similar to the way that color changes in production, but |
| // it may not match exactly. |
| auto* theme = ui::NativeTheme::GetInstanceForNativeUi(); |
| theme->set_user_color(color); |
| theme->NotifyOnNativeThemeUpdated(); |
| } |
| |
| // TODO(b/292584649): Remove this shortcut after testing is complete. |
| void HandleClearKMeansPref() { |
| if (auto* controller = Shell::Get()->session_controller(); |
| !(controller && controller->IsActiveUserSessionStarted())) { |
| return; |
| } |
| |
| const UserSession* session = |
| Shell::Get()->session_controller()->GetUserSession(/*index=*/0); |
| const AccountId& account_id = session->user_info.account_id; |
| PrefService* pref_service = |
| Shell::Get()->session_controller()->GetUserPrefServiceForUser(account_id); |
| pref_service->ClearPref(prefs::kDynamicColorUseKMeans); |
| |
| // Setting the color scheme is a visual indicator that the pref has been |
| // cleared. Tonal spot is the default color scheme, which is necessary to see |
| // the k means color. |
| Shell::Get()->color_palette_controller()->SetColorScheme( |
| style::mojom::ColorScheme::kTonalSpot, account_id, base::DoNothing()); |
| } |
| |
| void HandleTogglePowerButtonMenu() { |
| auto* controller = Shell::Get()->power_button_controller(); |
| controller->ShowMenuOnDebugAccelerator(); |
| } |
| |
| void HandleToggleKeyboardBacklight() { |
| base::RecordAction(base::UserMetricsAction("Accel_Keyboard_Backlight")); |
| accelerators::ToggleKeyboardBacklight(); |
| } |
| |
| void HandleToggleMicrophoneMute() { |
| base::RecordAction(base::UserMetricsAction("Accel_Microphone_Mute")); |
| accelerators::MicrophoneMuteToggle(); |
| } |
| |
| void HandleToggleTouchpad() { |
| base::RecordAction(base::UserMetricsAction("Accel_Toggle_Touchpad")); |
| Shell::Get()->touch_devices_controller()->ToggleTouchpad(); |
| } |
| |
| void HandleToggleTouchscreen() { |
| base::RecordAction(base::UserMetricsAction("Accel_Toggle_Touchscreen")); |
| TouchDevicesController* controller = Shell::Get()->touch_devices_controller(); |
| controller->SetTouchscreenEnabled( |
| !controller->GetTouchscreenEnabled(TouchDeviceEnabledSource::USER_PREF), |
| TouchDeviceEnabledSource::USER_PREF); |
| } |
| |
| void HandleToggleTabletMode() { |
| Shell::Get()->tablet_mode_controller()->SetEnabledForDev( |
| !display::Screen::Get()->InTabletMode()); |
| } |
| |
| void HandleToggleVideoConferenceCameraTrayIcon() { |
| if (!ash::features::IsVideoConferenceEnabled()) { |
| return; |
| } |
| |
| // Update media state to toggle video conference tray visibility. |
| const bool vc_tray_visible = Shell::Get() |
| ->GetPrimaryRootWindowController() |
| ->GetStatusAreaWidget() |
| ->video_conference_tray() |
| ->GetVisible(); |
| |
| VideoConferenceMediaState state; |
| state.has_media_app = !vc_tray_visible; |
| state.has_camera_permission = !vc_tray_visible; |
| state.has_microphone_permission = !vc_tray_visible; |
| state.is_capturing_screen = !vc_tray_visible; |
| VideoConferenceTrayController::Get()->UpdateWithMediaState(state); |
| } |
| |
| void HandleTriggerCrash() { |
| LOG(FATAL) << "Intentional crash via debug accelerator."; |
| } |
| |
| void HandleTriggerHUDDisplay() { |
| hud_display::HUDDisplayView::Toggle(); |
| } |
| |
| void HandleToggleVirtualTrackpad() { |
| VirtualTrackpadView::Toggle(); |
| } |
| |
| void HandleShowInformedRestore() { |
| if (auto* pine_controller = Shell::Get()->informed_restore_controller()) { |
| pine_controller->MaybeStartInformedRestoreSessionDevAccelerator(); |
| } |
| } |
| |
| // Toast debug shortcut constants. |
| const std::u16string oneline_toast_text = u"SystemUI toast text string"; |
| const std::u16string multiline_toast_text = |
| u"SystemUI toast text string that breaks to two lines due to accomodate " |
| u"long strings or translations. The text container has a max-width of " |
| u"512px."; |
| |
| void HandleShowToast() { |
| // Iterates through all toast variations, which are a combination of having |
| // multi-line text, a leading icon, and a text or icon button. |
| // `has_multiline_text` changes value every 6 iterations. |
| // `has_leading_icon` changes value every 3 iterations. |
| // `button_type` changes value every iteration. |
| static int index = 0; |
| const bool has_multiline_text = (index / 6) % 2; |
| const bool has_leading_icon = (index / 3) % 2; |
| const auto button_type = static_cast<ToastData::ButtonType>(index % 3); |
| index++; |
| |
| ToastData toast_data( |
| /*id=*/"id", ToastCatalogName::kDebugCommand, |
| has_multiline_text ? multiline_toast_text : oneline_toast_text, |
| ToastData::kDefaultToastDuration, |
| /*visible_on_lock_screen=*/true); |
| if (has_leading_icon) { |
| toast_data.leading_icon = &kSystemMenuBusinessIcon; |
| } |
| toast_data.button_type = button_type; |
| switch (button_type) { |
| case ToastData::ButtonType::kNone: |
| break; |
| case ToastData::ButtonType::kTextButton: |
| toast_data.button_text = u"Dismiss"; |
| break; |
| case ToastData::ButtonType::kIconButton: |
| toast_data.button_text = u"Feedback"; |
| toast_data.button_icon = &kFeedbackIcon; |
| break; |
| } |
| Shell::Get()->toast_manager()->Show(std::move(toast_data)); |
| } |
| |
| // Iterates through different system nudge variations: |
| // 1. Body text only |
| // 2. Body text and buttons |
| // 3. Title, body text and buttons |
| // 4. Image, title, body text and buttons |
| void HandleShowSystemNudge() { |
| static int index = 0; |
| bool use_long_text = index > 0; |
| bool has_buttons = index > 1; |
| bool has_title = index > 2; |
| bool has_image = index > 3; |
| ++index %= 5; |
| |
| const std::u16string title_text = u"Title text"; |
| const std::u16string short_body_text = u"Nudge body text"; |
| const std::u16string long_body_text = |
| u"Nudge body text should be clear, short and succinct (80 characters " |
| u"recommended)"; |
| |
| AnchoredNudgeData nudge_data( |
| /*id=*/"id", NudgeCatalogName::kTestCatalogName, |
| use_long_text ? long_body_text : short_body_text); |
| |
| if (has_title) { |
| nudge_data.title_text = title_text; |
| } |
| |
| if (has_image) { |
| nudge_data.image_model = ui::ImageModel::FromVectorIcon( |
| vector_icons::kDogfoodIcon, kColorAshIconColorPrimary, |
| /*icon_size=*/60); |
| } |
| |
| if (has_buttons) { |
| nudge_data.primary_button_text = u"Primary"; |
| nudge_data.secondary_button_text = u"Secondary"; |
| } |
| |
| Shell::Get()->anchored_nudge_manager()->Show(nudge_data); |
| } |
| |
| void HandleStartSunfishSession() { |
| if (CanShowSunfishOrScannerUi() && |
| !Shell::Get()->session_controller()->IsUserSessionBlocked()) { |
| RecordScannerFeatureUserState( |
| ScannerFeatureUserState::kSunfishSessionStartedFromDebugShortcut); |
| CaptureModeController::Get()->StartSunfishSession(); |
| } |
| } |
| |
| // TODO(b/318897434): Remove this shortcut after testing is complete. |
| void HandleToggleFocusModeState() { |
| auto* controller = FocusModeController::Get(); |
| switch (controller->GetSnapshot(base::Time::Now()).state) { |
| case FocusModeSession::State::kOn: |
| controller->TriggerEndingMomentImmediately(); |
| return; |
| case FocusModeSession::State::kEnding: |
| controller->ResetFocusSession(); |
| return; |
| default: |
| controller->ToggleFocusMode(); |
| return; |
| } |
| } |
| |
| } // namespace |
| |
| void PrintUIHierarchies() { |
| // This is a separate command so the user only has to hit one key to generate |
| // all the logs. Developers use the individual dumps repeatedly, so keep |
| // those as separate commands to avoid spamming their logs. |
| HandlePrintLayerHierarchy(); |
| HandlePrintWindowHierarchy(); |
| HandlePrintViewHierarchy(); |
| } |
| |
| bool DebugAcceleratorsEnabled() { |
| return base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kAshDebugShortcuts); |
| } |
| |
| bool DeveloperAcceleratorsEnabled() { |
| return base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kAshDeveloperShortcuts); |
| } |
| |
| void PerformDebugActionIfEnabled(AcceleratorAction action) { |
| if (!DebugAcceleratorsEnabled()) |
| return; |
| |
| switch (action) { |
| case AcceleratorAction::kDebugKeyboardBacklightToggle: |
| HandleToggleKeyboardBacklight(); |
| break; |
| case AcceleratorAction::kDebugMicrophoneMuteToggle: |
| HandleToggleMicrophoneMute(); |
| break; |
| case AcceleratorAction::kDebugPrintLayerHierarchy: |
| HandlePrintLayerHierarchy(); |
| break; |
| case AcceleratorAction::kDebugPrintViewHierarchy: |
| HandlePrintViewHierarchy(); |
| break; |
| case AcceleratorAction::kDebugPrintWindowHierarchy: |
| HandlePrintWindowHierarchy(); |
| break; |
| case AcceleratorAction::kDebugStartSunfishSession: |
| HandleStartSunfishSession(); |
| break; |
| case AcceleratorAction::kDebugShowInformedRestore: |
| HandleShowInformedRestore(); |
| break; |
| case AcceleratorAction::kDebugShowToast: |
| HandleShowToast(); |
| break; |
| case AcceleratorAction::kDebugShowSystemNudge: |
| HandleShowSystemNudge(); |
| break; |
| case AcceleratorAction::kDebugSystemUiStyleViewer: |
| SystemUIComponentsStyleViewerView::CreateAndShowWidget(); |
| break; |
| case AcceleratorAction::kDebugToggleDarkMode: |
| HandleToggleDarkMode(); |
| break; |
| case AcceleratorAction::kDebugToggleDynamicColor: |
| HandleToggleDynamicColor(); |
| break; |
| case AcceleratorAction::kDebugClearUseKMeansPref: |
| HandleClearKMeansPref(); |
| break; |
| case AcceleratorAction::kDebugToggleFocusModeState: |
| HandleToggleFocusModeState(); |
| break; |
| case AcceleratorAction::kDebugTogglePowerButtonMenu: |
| HandleTogglePowerButtonMenu(); |
| break; |
| case AcceleratorAction::kDebugToggleTouchPad: |
| HandleToggleTouchpad(); |
| break; |
| case AcceleratorAction::kDebugToggleTouchScreen: |
| HandleToggleTouchscreen(); |
| break; |
| case AcceleratorAction::kDebugToggleTabletMode: |
| HandleToggleTabletMode(); |
| break; |
| case AcceleratorAction::kDebugToggleWallpaperMode: |
| HandleToggleWallpaperMode(); |
| break; |
| case AcceleratorAction::kDebugTriggerCrash: |
| HandleTriggerCrash(); |
| break; |
| case AcceleratorAction::kDebugToggleHudDisplay: |
| HandleTriggerHUDDisplay(); |
| break; |
| case AcceleratorAction::kDebugToggleVirtualTrackpad: |
| HandleToggleVirtualTrackpad(); |
| break; |
| case AcceleratorAction::kDebugToggleVideoConferenceCameraTrayIcon: |
| HandleToggleVideoConferenceCameraTrayIcon(); |
| break; |
| case AcceleratorAction::kDebugShowTestWindow: |
| #if !BUILDFLAG(IS_CHROMEOS_DEVICE) |
| OpenTestAppWindow(/*client_controlled=*/false); |
| #endif |
| break; |
| default: |
| break; |
| } |
| } |
| |
| } // namespace debug |
| } // namespace ash |