// 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> parsed_json =
base::JSONReader::Read(location_info);
if (!parsed_json || !parsed_json->is_dict()) {
LOG(ERROR) << "JSONReader failed reading side volume button location info: "
<< location_info;
return;
}
const base::Value::Dict& info_in_dict = parsed_json->GetDict();
const std::string* region = info_in_dict.FindString(kVolumeButtonRegion);
if (region)
side_volume_button_location_.region = *region;
const std::string* side = info_in_dict.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