blob: 583c8689c4476460ecf1c19429e3be07f14a39de [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/arc/arc_util.h"
#include <algorithm>
#include <cstdio>
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/app_types.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/process/launch.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "chromeos/dbus/concierge/concierge_client.h"
#include "chromeos/dbus/debug_daemon/debug_daemon_client.h"
#include "chromeos/dbus/session_manager/session_manager_client.h"
#include "chromeos/dbus/upstart/upstart_client.h"
#include "components/arc/arc_features.h"
#include "components/exo/shell_surface_util.h"
#include "components/user_manager/user_manager.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
#include "ui/display/types/display_constants.h"
namespace arc {
namespace {
// This is for finch. See also crbug.com/633704 for details.
// TODO(hidehiko): More comments of the intention how this works, when
// we unify the commandline flags.
const base::Feature kEnableArcFeature{"EnableARC",
base::FEATURE_DISABLED_BY_DEFAULT};
// Possible values for --arc-availability flag.
constexpr char kAvailabilityNone[] = "none";
constexpr char kAvailabilityInstalled[] = "installed";
constexpr char kAvailabilityOfficiallySupported[] = "officially-supported";
constexpr char kAlwaysStartWithNoPlayStore[] =
"always-start-with-no-play-store";
constexpr const char kCrosSystemPath[] = "/usr/bin/crossystem";
// ArcVmUreadaheadMode param value strings.
constexpr char kGenerate[] = "generate";
constexpr char kDisabled[] = "disabled";
void SetArcCpuRestrictionCallback(
login_manager::ContainerCpuRestrictionState state,
bool success) {
if (success)
return;
const char* message =
(state == login_manager::CONTAINER_CPU_RESTRICTION_BACKGROUND)
? "unprioritize"
: "prioritize";
LOG(ERROR) << "Failed to " << message << " ARC";
}
void OnSetArcVmCpuRestriction(
absl::optional<vm_tools::concierge::SetVmCpuRestrictionResponse> response) {
if (!response) {
LOG(ERROR) << "Failed to call SetVmCpuRestriction";
return;
}
if (!response->success())
LOG(ERROR) << "SetVmCpuRestriction for ARCVM failed";
}
void SetArcVmCpuRestriction(CpuRestrictionState cpu_restriction_state) {
auto* client = chromeos::ConciergeClient::Get();
if (!client) {
LOG(ERROR) << "ConciergeClient is not available";
return;
}
vm_tools::concierge::SetVmCpuRestrictionRequest request;
request.set_cpu_cgroup(vm_tools::concierge::CPU_CGROUP_ARCVM);
switch (cpu_restriction_state) {
case CpuRestrictionState::CPU_RESTRICTION_FOREGROUND:
request.set_cpu_restriction_state(
vm_tools::concierge::CPU_RESTRICTION_FOREGROUND);
break;
case CpuRestrictionState::CPU_RESTRICTION_BACKGROUND:
request.set_cpu_restriction_state(
vm_tools::concierge::CPU_RESTRICTION_BACKGROUND);
break;
}
client->SetVmCpuRestriction(request,
base::BindOnce(&OnSetArcVmCpuRestriction));
}
void SetArcContainerCpuRestriction(CpuRestrictionState cpu_restriction_state) {
if (!chromeos::SessionManagerClient::Get()) {
LOG(WARNING) << "SessionManagerClient is not available";
return;
}
login_manager::ContainerCpuRestrictionState state;
switch (cpu_restriction_state) {
case CpuRestrictionState::CPU_RESTRICTION_FOREGROUND:
state = login_manager::CONTAINER_CPU_RESTRICTION_FOREGROUND;
break;
case CpuRestrictionState::CPU_RESTRICTION_BACKGROUND:
state = login_manager::CONTAINER_CPU_RESTRICTION_BACKGROUND;
break;
}
chromeos::SessionManagerClient::Get()->SetArcCpuRestriction(
state, base::BindOnce(SetArcCpuRestrictionCallback, state));
}
// Decodes a job name that may have "_2d" e.g. |kArcCreateDataJobName|
// and returns a decoded string.
std::string DecodeJobName(const std::string& raw_job_name) {
constexpr const char* kFind = "_2d";
std::string decoded(raw_job_name);
base::ReplaceSubstringsAfterOffset(&decoded, 0, kFind, "-");
return decoded;
}
// Called when the Upstart operation started in ConfigureUpstartJobs is
// done. Handles the fatal error (if any) and then starts the next job.
void OnConfigureUpstartJobs(std::deque<JobDesc> jobs,
chromeos::VoidDBusMethodCallback callback,
bool result) {
const std::string job_name = DecodeJobName(jobs.front().job_name);
const bool is_start = (jobs.front().operation == UpstartOperation::JOB_START);
if (!result && is_start) {
LOG(ERROR) << "Failed to start " << job_name;
// TODO(yusukes): Record UMA for this case.
std::move(callback).Run(false);
return;
}
VLOG(1) << job_name
<< (is_start ? " started" : (result ? " stopped " : " not running?"));
jobs.pop_front();
ConfigureUpstartJobs(std::move(jobs), std::move(callback));
}
} // namespace
bool IsArcAvailable() {
const auto* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(chromeos::switches::kArcAvailability)) {
const std::string value =
command_line->GetSwitchValueASCII(chromeos::switches::kArcAvailability);
DCHECK(value == kAvailabilityNone || value == kAvailabilityInstalled ||
value == kAvailabilityOfficiallySupported)
<< "Unknown flag value: " << value;
return value == kAvailabilityOfficiallySupported ||
(value == kAvailabilityInstalled &&
base::FeatureList::IsEnabled(kEnableArcFeature));
}
// For transition, fallback to old flags.
// TODO(hidehiko): Remove this and clean up whole this function, when
// session_manager supports a new flag.
return command_line->HasSwitch(chromeos::switches::kEnableArc) ||
(command_line->HasSwitch(chromeos::switches::kArcAvailable) &&
base::FeatureList::IsEnabled(kEnableArcFeature));
}
bool IsArcVmEnabled() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kEnableArcVm);
}
bool IsArcVmRtVcpuEnabled(uint32_t cpus) {
// TODO(kansho): remove switch after tast test use Finch instead.
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kEnableArcVmRtVcpu)) {
return true;
}
if (cpus == 2 && base::FeatureList::IsEnabled(kRtVcpuDualCore))
return true;
if (cpus > 2 && base::FeatureList::IsEnabled(kRtVcpuQuadCore))
return true;
return false;
}
bool IsArcVmUseHugePages() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kArcVmUseHugePages);
}
bool IsArcVmDevConfIgnored() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kIgnoreArcVmDevConf);
}
ArcVmUreadaheadMode GetArcVmUreadaheadMode() {
ArcVmUreadaheadMode mode = ArcVmUreadaheadMode::READAHEAD;
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kArcVmUreadaheadMode)) {
const std::string value =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
chromeos::switches::kArcVmUreadaheadMode);
if (value == kGenerate) {
mode = ArcVmUreadaheadMode::GENERATE;
} else if (value == kDisabled) {
mode = ArcVmUreadaheadMode::DISABLED;
} else {
LOG(ERROR) << "Invalid parameter " << value << " for "
<< chromeos::switches::kArcVmUreadaheadMode;
}
}
return mode;
}
bool ShouldArcAlwaysStart() {
const auto* command_line = base::CommandLine::ForCurrentProcess();
if (!command_line->HasSwitch(chromeos::switches::kArcStartMode))
return false;
return command_line->GetSwitchValueASCII(chromeos::switches::kArcStartMode) ==
kAlwaysStartWithNoPlayStore;
}
bool ShouldArcAlwaysStartWithNoPlayStore() {
return base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
chromeos::switches::kArcStartMode) == kAlwaysStartWithNoPlayStore;
}
bool ShouldShowOptInForTesting() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kArcForceShowOptInUi);
}
void SetArcAlwaysStartWithoutPlayStoreForTesting() {
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
chromeos::switches::kArcStartMode, kAlwaysStartWithNoPlayStore);
}
bool IsArcKioskAvailable() {
const auto* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(chromeos::switches::kArcAvailability)) {
std::string value =
command_line->GetSwitchValueASCII(chromeos::switches::kArcAvailability);
if (value == kAvailabilityInstalled)
return true;
return IsArcAvailable();
}
// TODO(hidehiko): Remove this when session_manager supports the new flag.
if (command_line->HasSwitch(chromeos::switches::kArcAvailable))
return true;
// If not special kiosk device case, use general ARC check.
return IsArcAvailable();
}
void SetArcAvailableCommandLineForTesting(base::CommandLine* command_line) {
command_line->AppendSwitchASCII(chromeos::switches::kArcAvailability,
kAvailabilityOfficiallySupported);
}
bool IsArcKioskMode() {
return user_manager::UserManager::IsInitialized() &&
user_manager::UserManager::Get()->IsLoggedInAsArcKioskApp();
}
bool IsRobotOrOfflineDemoAccountMode() {
return user_manager::UserManager::IsInitialized() &&
(user_manager::UserManager::Get()->IsLoggedInAsArcKioskApp() ||
user_manager::UserManager::Get()->IsLoggedInAsPublicAccount());
}
bool IsArcAllowedForUser(const user_manager::User* user) {
if (!user) {
VLOG(1) << "No ARC for nullptr user.";
return false;
}
// ARC is only supported for the following cases:
// - Users have Gaia accounts;
// - Active directory users;
// - ARC kiosk session;
// - Public Session users;
// USER_TYPE_ARC_KIOSK_APP check is compatible with IsArcKioskMode()
// above because ARC kiosk user is always the primary/active user of a
// user session. The same for USER_TYPE_PUBLIC_ACCOUNT.
if (!user->HasGaiaAccount() && !user->IsActiveDirectoryUser() &&
user->GetType() != user_manager::USER_TYPE_ARC_KIOSK_APP &&
user->GetType() != user_manager::USER_TYPE_PUBLIC_ACCOUNT) {
VLOG(1) << "Users without GAIA or AD accounts, or not ARC kiosk apps are "
"not supported in ARC.";
return false;
}
return true;
}
bool IsArcOptInVerificationDisabled() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kDisableArcOptInVerification);
}
int GetWindowTaskId(const aura::Window* window) {
if (!window)
return kNoTaskId;
const std::string* arc_app_id = exo::GetShellApplicationId(window);
if (!arc_app_id)
return kNoTaskId;
return GetTaskIdFromWindowAppId(*arc_app_id);
}
int GetTaskIdFromWindowAppId(const std::string& app_id) {
int task_id;
if (std::sscanf(app_id.c_str(), "org.chromium.arc.%d", &task_id) != 1)
return kNoTaskId;
return task_id;
}
void SetArcCpuRestriction(CpuRestrictionState cpu_restriction_state) {
// Ignore any calls to restrict the ARC container if the specified command
// line flag is set.
if (chromeos::switches::IsArcCpuRestrictionDisabled() &&
cpu_restriction_state == CpuRestrictionState::CPU_RESTRICTION_BACKGROUND)
return;
if (IsArcVmEnabled()) {
SetArcVmCpuRestriction(cpu_restriction_state);
} else {
SetArcContainerCpuRestriction(cpu_restriction_state);
}
}
bool IsArcForceCacheAppIcon() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kArcGeneratePlayAutoInstall);
}
bool IsArcDataCleanupOnStartRequested() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kArcDataCleanupOnStart);
}
bool IsArcAppSyncFlowDisabled() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kArcDisableAppSync);
}
bool IsArcLocaleSyncDisabled() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kArcDisableLocaleSync);
}
bool IsArcPlayAutoInstallDisabled() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kArcDisablePlayAutoInstall);
}
int32_t GetLcdDensityForDeviceScaleFactor(float device_scale_factor) {
const auto* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(chromeos::switches::kArcScale)) {
const std::string dpi_str =
command_line->GetSwitchValueASCII(chromeos::switches::kArcScale);
int dpi;
if (base::StringToInt(dpi_str, &dpi))
return dpi;
VLOG(1) << "Invalid Arc scale set. Using default.";
}
// TODO(b/131884992): Remove the logic to update default lcd density once
// per-display-density is supported.
constexpr float kEpsilon = 0.001;
if (std::abs(device_scale_factor - display::kDsf_2_252) < kEpsilon)
return 280;
if (std::abs(device_scale_factor - 2.4f) < kEpsilon)
return 280;
if (std::abs(device_scale_factor - 1.6f) < kEpsilon)
return 213; // TVDPI
if (std::abs(device_scale_factor - display::kDsf_1_777) < kEpsilon)
return 240; // HDPI
if (std::abs(device_scale_factor - display::kDsf_1_8) < kEpsilon)
return 240; // HDPI
if (std::abs(device_scale_factor - display::kDsf_2_666) < kEpsilon)
return 320; // XHDPI
constexpr float kChromeScaleToAndroidScaleRatio = 0.75f;
constexpr int32_t kDefaultDensityDpi = 160;
return static_cast<int32_t>(
std::max(1.0f, device_scale_factor * kChromeScaleToAndroidScaleRatio) *
kDefaultDensityDpi);
}
int GetSystemPropertyInt(const std::string& property) {
std::string output;
if (!base::GetAppOutput({kCrosSystemPath, property}, &output))
return -1;
int output_int;
return base::StringToInt(output, &output_int) ? output_int : -1;
}
JobDesc::JobDesc(const std::string& job_name,
UpstartOperation operation,
const std::vector<std::string>& environment)
: job_name(job_name), operation(operation), environment(environment) {}
JobDesc::~JobDesc() = default;
JobDesc::JobDesc(const JobDesc& other) = default;
void ConfigureUpstartJobs(std::deque<JobDesc> jobs,
chromeos::VoidDBusMethodCallback callback) {
if (jobs.empty()) {
std::move(callback).Run(true);
return;
}
if (jobs.front().operation == UpstartOperation::JOB_STOP_AND_START) {
// Expand the restart operation into two, stop and start.
jobs.front().operation = UpstartOperation::JOB_START;
jobs.push_front({jobs.front().job_name, UpstartOperation::JOB_STOP,
jobs.front().environment});
}
const auto& job_name = jobs.front().job_name;
const auto& operation = jobs.front().operation;
const auto& environment = jobs.front().environment;
VLOG(1) << (operation == UpstartOperation::JOB_START ? "Starting "
: "Stopping ")
<< DecodeJobName(job_name);
auto wrapped_callback = base::BindOnce(&OnConfigureUpstartJobs,
std::move(jobs), std::move(callback));
switch (operation) {
case UpstartOperation::JOB_START:
chromeos::UpstartClient::Get()->StartJob(job_name, environment,
std::move(wrapped_callback));
break;
case UpstartOperation::JOB_STOP:
chromeos::UpstartClient::Get()->StopJob(job_name, environment,
std::move(wrapped_callback));
break;
case UpstartOperation::JOB_STOP_AND_START:
NOTREACHED();
break;
}
}
} // namespace arc