| // Copyright 2022 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/tablet_volume_controller.h" |
| |
| #include "ash/constants/ash_switches.h" |
| #include "ash/display/screen_orientation_controller.h" |
| #include "ash/shell.h" |
| #include "base/command_line.h" |
| #include "base/files/file_util.h" |
| #include "base/json/json_reader.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "chromeos/ash/components/audio/cras_audio_handler.h" |
| #include "ui/events/devices/device_data_manager.h" |
| |
| namespace ash { |
| namespace { |
| |
| // Path of the json file that contains side volume button location info. |
| constexpr char kSideVolumeButtonLocationFilePath[] = |
| "/usr/share/chromeos-assets/side_volume_button/location.json"; |
| |
| // The interval between two volume control actions within one volume adjust. |
| constexpr base::TimeDelta kVolumeAdjustTimeout = base::Seconds(2); |
| |
| void RecordTabletVolumeAdjustTypeHistogram(TabletModeVolumeAdjustType type) { |
| UMA_HISTOGRAM_ENUMERATION(kTabletCountOfVolumeAdjustType, type); |
| } |
| |
| } // namespace |
| |
| const char kTabletCountOfVolumeAdjustType[] = "Tablet.CountOfVolumeAdjustType"; |
| |
| // Fields of the side volume button location info. |
| const char kVolumeButtonRegion[] = "region"; |
| const char kVolumeButtonSide[] = "side"; |
| |
| // Values of kVolumeButtonRegion. |
| const char kVolumeButtonRegionKeyboard[] = "keyboard"; |
| const char kVolumeButtonRegionScreen[] = "screen"; |
| // Values of kVolumeButtonSide. |
| const char kVolumeButtonSideLeft[] = "left"; |
| const char kVolumeButtonSideRight[] = "right"; |
| const char kVolumeButtonSideTop[] = "top"; |
| const char kVolumeButtonSideBottom[] = "bottom"; |
| |
| TabletVolumeController::TabletVolumeController() |
| : side_volume_button_location_file_path_( |
| base::FilePath(kSideVolumeButtonLocationFilePath)) { |
| ParseSideVolumeButtonLocationInfo(); |
| } |
| |
| TabletVolumeController::~TabletVolumeController() = default; |
| |
| void TabletVolumeController::ParseSideVolumeButtonLocationInfo() { |
| std::string location_info; |
| const base::CommandLine* cl = base::CommandLine::ForCurrentProcess(); |
| if (cl->HasSwitch(switches::kAshSideVolumeButtonPosition)) { |
| location_info = |
| cl->GetSwitchValueASCII(switches::kAshSideVolumeButtonPosition); |
| } else if (!base::PathExists(side_volume_button_location_file_path_) || |
| !base::ReadFileToString(side_volume_button_location_file_path_, |
| &location_info) || |
| location_info.empty()) { |
| return; |
| } |
| |
| std::optional<base::Value::Dict> parsed_json = base::JSONReader::ReadDict( |
| location_info, base::JSON_PARSE_CHROMIUM_EXTENSIONS); |
| if (!parsed_json) { |
| LOG(ERROR) << "JSONReader failed reading side volume button location info: " |
| << location_info; |
| return; |
| } |
| |
| const std::string* region = parsed_json->FindString(kVolumeButtonRegion); |
| if (region) |
| side_volume_button_location_.region = *region; |
| |
| const std::string* side = parsed_json->FindString(kVolumeButtonSide); |
| if (side) |
| side_volume_button_location_.side = *side; |
| } |
| |
| bool TabletVolumeController::IsValidSideVolumeButtonLocation() const { |
| const std::string region = side_volume_button_location_.region; |
| const std::string side = side_volume_button_location_.side; |
| if (region != kVolumeButtonRegionKeyboard && |
| region != kVolumeButtonRegionScreen) { |
| return false; |
| } |
| if (side != kVolumeButtonSideLeft && side != kVolumeButtonSideRight && |
| side != kVolumeButtonSideTop && side != kVolumeButtonSideBottom) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool TabletVolumeController::ShouldSwapSideVolumeButtons( |
| int source_device_id) const { |
| if (!IsInternalKeyboardOrUncategorizedDevice(source_device_id)) |
| return false; |
| |
| if (!IsValidSideVolumeButtonLocation()) |
| return false; |
| |
| chromeos::OrientationType screen_orientation = |
| Shell::Get()->screen_orientation_controller()->GetCurrentOrientation(); |
| const std::string side = side_volume_button_location_.side; |
| const bool is_landscape_secondary_or_portrait_primary = |
| screen_orientation == chromeos::OrientationType::kLandscapeSecondary || |
| screen_orientation == chromeos::OrientationType::kPortraitPrimary; |
| |
| if (side_volume_button_location_.region == kVolumeButtonRegionKeyboard) { |
| if (side == kVolumeButtonSideLeft || side == kVolumeButtonSideRight) |
| return chromeos::IsPrimaryOrientation(screen_orientation); |
| return is_landscape_secondary_or_portrait_primary; |
| } |
| |
| DCHECK_EQ(kVolumeButtonRegionScreen, side_volume_button_location_.region); |
| if (side == kVolumeButtonSideLeft || side == kVolumeButtonSideRight) |
| return !chromeos::IsPrimaryOrientation(screen_orientation); |
| return is_landscape_secondary_or_portrait_primary; |
| } |
| |
| void TabletVolumeController::UpdateTabletModeVolumeAdjustHistogram() { |
| const int volume_percent = CrasAudioHandler::Get()->GetOutputVolumePercent(); |
| if ((volume_adjust_starts_with_up_ && |
| volume_percent >= initial_volume_percent_) || |
| (!volume_adjust_starts_with_up_ && |
| volume_percent <= initial_volume_percent_)) { |
| RecordTabletVolumeAdjustTypeHistogram( |
| TabletModeVolumeAdjustType::kNormalAdjustWithSwapEnabled); |
| } else { |
| RecordTabletVolumeAdjustTypeHistogram( |
| TabletModeVolumeAdjustType::kAccidentalAdjustWithSwapEnabled); |
| } |
| } |
| |
| bool TabletVolumeController::IsInternalKeyboardOrUncategorizedDevice( |
| int source_device_id) const { |
| if (source_device_id == ui::ED_UNKNOWN_DEVICE) |
| return false; |
| |
| for (const ui::InputDevice& keyboard : |
| ui::DeviceDataManager::GetInstance()->GetKeyboardDevices()) { |
| if (keyboard.type == ui::InputDeviceType::INPUT_DEVICE_INTERNAL && |
| keyboard.id == source_device_id) { |
| return true; |
| } |
| } |
| |
| for (const ui::InputDevice& uncategorized_device : |
| ui::DeviceDataManager::GetInstance()->GetUncategorizedDevices()) { |
| if (uncategorized_device.id == source_device_id && |
| uncategorized_device.type == |
| ui::InputDeviceType::INPUT_DEVICE_INTERNAL) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void TabletVolumeController::StartTabletModeVolumeAdjustTimer( |
| bool is_volume_up) { |
| if (!tablet_mode_volume_adjust_timer_.IsRunning()) { |
| volume_adjust_starts_with_up_ = is_volume_up; |
| initial_volume_percent_ = CrasAudioHandler::Get()->GetOutputVolumePercent(); |
| } |
| tablet_mode_volume_adjust_timer_.Start( |
| FROM_HERE, kVolumeAdjustTimeout, this, |
| &TabletVolumeController::UpdateTabletModeVolumeAdjustHistogram); |
| } |
| |
| bool TabletVolumeController::TriggerTabletModeVolumeAdjustTimerForTest() { |
| if (!tablet_mode_volume_adjust_timer_.IsRunning()) |
| return false; |
| |
| tablet_mode_volume_adjust_timer_.FireNow(); |
| return true; |
| } |
| |
| void TabletVolumeController::SetSideVolumeButtonFilePathForTest( |
| base::FilePath path) { |
| side_volume_button_location_file_path_ = path; |
| ParseSideVolumeButtonLocationInfo(); |
| } |
| |
| void TabletVolumeController::SetSideVolumeButtonLocationForTest( |
| const std::string& region, |
| const std::string& side) { |
| side_volume_button_location_.region = region; |
| side_volume_button_location_.side = side; |
| } |
| |
| const TabletVolumeController::SideVolumeButtonLocation& |
| TabletVolumeController::GetSideVolumeButtonLocationForTest() const { |
| return side_volume_button_location_; |
| } |
| |
| } // namespace ash |