blob: 26fba0dee9ae1e356151e3bba01bb7aef1fb678c [file] [log] [blame]
// Copyright 2017 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/components/arc/arc_util.h"
#include <algorithm>
#include <cstdio>
#include "ash/components/arc/arc_features.h"
#include "ash/components/arc/arc_prefs.h"
#include "ash/components/arc/session/arc_vm_data_migration_status.h"
#include "ash/constants/app_types.h"
#include "ash/constants/ash_switches.h"
#include "ash/system/time/calendar_utils.h"
#include "ash/system/time/date_helper.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/process/launch.h"
#include "base/process/process_metrics.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "chromeos/ash/components/dbus/debug_daemon/debug_daemon_client.h"
#include "chromeos/ash/components/dbus/upstart/upstart_client.h"
#include "chromeos/version/version_loader.h"
#include "components/exo/shell_surface_util.h"
#include "components/prefs/pref_service.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.
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 char kManualStart[] = "manual";
constexpr const char kCrosSystemPath[] = "/usr/bin/crossystem";
// ArcVmUreadaheadMode param value strings.
constexpr char kReadahead[] = "readahead";
constexpr char kGenerate[] = "generate";
constexpr char kDisabled[] = "disabled";
// 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(khmel): 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));
}
int64_t GetRequiredDiskImageSizeForArcVmDataMigrationInBytes(
uint64_t android_data_size_in_bytes) {
// Reserved disk space for virtio-blk /data disk image (128 MB). Defined in
// the guest's arc-mkfs-blk-data.
constexpr uint64_t kReservedDiskSpaceInBytes = 128ULL << 20;
return android_data_size_in_bytes * 11ULL / 10ULL + kReservedDiskSpaceInBytes;
}
} // namespace
bool IsArcAvailable() {
const auto* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(ash::switches::kArcAvailability)) {
const std::string value =
command_line->GetSwitchValueASCII(ash::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(ash::switches::kEnableArc) ||
(command_line->HasSwitch(ash::switches::kArcAvailable) &&
base::FeatureList::IsEnabled(kEnableArcFeature));
}
bool IsArcVmEnabled() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kEnableArcVm);
}
int GetArcAndroidSdkVersionAsInt() {
const auto arc_version_str =
chromeos::version_loader::GetArcAndroidSdkVersion();
if (!arc_version_str) {
LOG(ERROR) << "ARC SDK version is unknown";
return kMaxArcVersion;
}
int arc_version;
if (!base::StringToInt(*arc_version_str, &arc_version)) {
LOG(WARNING) << "ARC SDK version is not a number: " << *arc_version_str;
return kMaxArcVersion;
}
return arc_version;
}
bool IsArcVmRtVcpuEnabled(uint32_t cpus) {
// TODO(kansho): remove switch after tast test use Finch instead.
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::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(
ash::switches::kArcVmUseHugePages);
}
bool IsArcVmDevConfIgnored() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kIgnoreArcVmDevConf);
}
bool IsUreadaheadDisabled() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kArcDisableUreadahead);
}
bool IsHostUreadaheadGeneration() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kArcHostUreadaheadGeneration);
}
ArcVmUreadaheadMode GetArcVmUreadaheadMode() {
ArcVmUreadaheadMode mode = IsUreadaheadDisabled()
? ArcVmUreadaheadMode::DISABLED
: ArcVmUreadaheadMode::READAHEAD;
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kArcVmUreadaheadMode)) {
const std::string value =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
ash::switches::kArcVmUreadaheadMode);
if (value == kReadahead) {
mode = ArcVmUreadaheadMode::READAHEAD;
} else if (value == kGenerate) {
mode = ArcVmUreadaheadMode::GENERATE;
} else if (value == kDisabled) {
mode = ArcVmUreadaheadMode::DISABLED;
} else {
LOG(ERROR) << "Invalid parameter " << value << " for "
<< ash::switches::kArcVmUreadaheadMode;
}
}
return mode;
}
bool ShouldArcAlwaysStart() {
return base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
ash::switches::kArcStartMode) == kAlwaysStartWithNoPlayStore;
}
bool ShouldArcAlwaysStartWithNoPlayStore() {
return base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
ash::switches::kArcStartMode) == kAlwaysStartWithNoPlayStore;
}
bool ShouldArcStartManually() {
return base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
ash::switches::kArcStartMode) == kManualStart;
}
bool ShouldShowOptInForTesting() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kArcForceShowOptInUi);
}
bool IsArcKioskAvailable() {
const auto* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(ash::switches::kArcAvailability)) {
std::string value =
command_line->GetSwitchValueASCII(ash::switches::kArcAvailability);
if (value == kAvailabilityInstalled)
return true;
return IsArcAvailable();
}
// TODO(hidehiko): Remove this when session_manager supports the new flag.
if (command_line->HasSwitch(ash::switches::kArcAvailable))
return true;
// If not special kiosk device case, use general ARC check.
return IsArcAvailable();
}
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(
ash::switches::kDisableArcOptInVerification);
}
absl::optional<int> GetWindowTaskId(const aura::Window* window) {
if (!window)
return absl::nullopt;
const std::string* window_app_id = exo::GetShellApplicationId(window);
if (!window_app_id)
return absl::nullopt;
return GetTaskIdFromWindowAppId(*window_app_id);
}
absl::optional<int> GetTaskIdFromWindowAppId(const std::string& window_app_id) {
int task_id;
if (std::sscanf(window_app_id.c_str(), "org.chromium.arc.%d", &task_id) != 1)
return absl::nullopt;
return task_id;
}
absl::optional<int> GetWindowSessionId(const aura::Window* window) {
if (!window)
return absl::nullopt;
const std::string* window_app_id = exo::GetShellApplicationId(window);
if (!window_app_id)
return absl::nullopt;
return GetSessionIdFromWindowAppId(*window_app_id);
}
absl::optional<int> GetSessionIdFromWindowAppId(
const std::string& window_app_id) {
int session_id;
if (std::sscanf(window_app_id.c_str(), "org.chromium.arc.session.%d",
&session_id) != 1) {
return absl::nullopt;
}
return session_id;
}
absl::optional<int> GetWindowTaskOrSessionId(const aura::Window* window) {
auto result = GetWindowTaskId(window);
if (result)
return result;
return GetWindowSessionId(window);
}
bool IsArcForceCacheAppIcon() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kArcGeneratePlayAutoInstall);
}
bool IsArcDataCleanupOnStartRequested() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kArcDataCleanupOnStart);
}
bool IsArcAppSyncFlowDisabled() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kArcDisableAppSync);
}
bool IsArcLocaleSyncDisabled() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kArcDisableLocaleSync);
}
bool IsArcPlayAutoInstallDisabled() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kArcDisablePlayAutoInstall);
}
int32_t GetLcdDensityForDeviceScaleFactor(float device_scale_factor) {
const auto* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(ash::switches::kArcScale)) {
const std::string dpi_str =
command_line->GetSwitchValueASCII(ash::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:
ash::UpstartClient::Get()->StartJob(job_name, environment,
std::move(wrapped_callback));
break;
case UpstartOperation::JOB_STOP:
ash::UpstartClient::Get()->StopJob(job_name, environment,
std::move(wrapped_callback));
break;
case UpstartOperation::JOB_STOP_AND_START:
NOTREACHED();
break;
}
}
ArcVmDataMigrationStatus GetArcVmDataMigrationStatus(PrefService* prefs) {
return static_cast<ArcVmDataMigrationStatus>(
prefs->GetInteger(prefs::kArcVmDataMigrationStatus));
}
void SetArcVmDataMigrationStatus(PrefService* prefs,
ArcVmDataMigrationStatus status) {
prefs->SetInteger(prefs::kArcVmDataMigrationStatus, static_cast<int>(status));
}
bool ShouldUseVirtioBlkData(PrefService* prefs) {
// If kEnableVirtioBlkForData is set, force using virtio-blk /data regardless
// of the migration status.
if (base::FeatureList::IsEnabled(kEnableVirtioBlkForData))
return true;
// Just use virtio-fs when ARCVM /data migration is not enabled.
if (!base::FeatureList::IsEnabled(kEnableArcVmDataMigration))
return false;
ArcVmDataMigrationStatus status = GetArcVmDataMigrationStatus(prefs);
if (status == ArcVmDataMigrationStatus::kFinished) {
VLOG(1) << "ARCVM /data migration has finished";
return true;
}
VLOG(1) << "ARCVM /data migration hasn't finished yet. Status=" << status;
return false;
}
int GetDaysUntilArcVmDataMigrationDeadline(PrefService* prefs) {
if (GetArcVmDataMigrationStatus(prefs) ==
ArcVmDataMigrationStatus::kStarted) {
// If ARCVM /data migration is in progress. Treat it in the same way as
// cases where the deadline is passed.
// TODO(b/258278176): Do not call this function when the migration is in
// progress, or return a different value (0) to provide a dedicated UI.
return 1;
}
const base::Time notification_first_shown_time =
prefs->GetTime(prefs::kArcVmDataMigrationNotificationFirstShownTime);
if (notification_first_shown_time == base::Time()) {
// The preference is uninitialized (the notification has not been shown).
LOG(ERROR) << "No deadline can be calculated because ARCVM /data migration "
"notification has not been shown before";
return kArcVmDataMigrationNumberOfDismissibleDays;
}
auto* date_helper = ash::DateHelper::GetInstance();
DCHECK(date_helper);
// Calculate the deadline assuming that the first notification was shown in
// the current timezone.
// ash::calendar_utils::kDurationForAdjustingDST is added to take into account
// days longer than 24 hours due to daylight saving time.
// For example, if the notification is shown for the first time at
// 2023-01-01T16:00:00Z and kArcVmDataMigrationNumberOfDismissibleDays is 30,
// the deadline will be 2023-01-31T00:00:00Z.
// This function will return 30 until 2023-01-01T23:59:99Z and keep returning
// 1 from 2023-01-30T00:00:00Z onward.
const base::Time deadline = date_helper->GetLocalMidnight(
date_helper->GetLocalMidnight(notification_first_shown_time) +
kArcVmDataMigrationDismissibleTimeDelta +
ash::calendar_utils::kDurationForAdjustingDST);
const base::Time last_local_midnight =
date_helper->GetLocalMidnight(base::Time::Now());
const base::TimeDelta delta =
last_local_midnight < deadline
? deadline - last_local_midnight +
ash::calendar_utils::kDurationForAdjustingDST
: base::Days(0);
const int delta_in_days = delta.InDays();
if (delta_in_days > kArcVmDataMigrationNumberOfDismissibleDays) {
return kArcVmDataMigrationNumberOfDismissibleDays;
}
return std::max(delta_in_days, 1);
}
bool ArcVmDataMigrationShouldBeDismissible(int days_until_deadline) {
return days_until_deadline > 1;
}
uint64_t GetDesiredDiskImageSizeForArcVmDataMigrationInBytes(
uint64_t android_data_size_in_bytes,
uint64_t free_disk_space_in_bytes) {
// Mask to make the disk image size a multiple of the block size (4096 bytes).
constexpr uint64_t kDiskImageSizeMaskInBytes = ~((4ULL << 10) - 1);
// Minimum disk image size for virtio-blk /data (4 GB).
constexpr uint64_t kMinimumDiskImageSizeInBytes = 4ULL << 30;
// The default disk image size set by Concierge.
const uint64_t default_disk_image_size_in_bytes =
free_disk_space_in_bytes * 9ULL / 10ULL;
const uint64_t required_disk_image_size_in_bytes =
GetRequiredDiskImageSizeForArcVmDataMigrationInBytes(
android_data_size_in_bytes);
return std::max(default_disk_image_size_in_bytes +
required_disk_image_size_in_bytes,
kMinimumDiskImageSizeInBytes) &
kDiskImageSizeMaskInBytes;
}
uint64_t GetRequiredFreeDiskSpaceForArcVmDataMigrationInBytes(
uint64_t android_data_size_in_bytes,
uint64_t free_disk_space_in_bytes) {
// Mask to make the required free disk space a multiple of 512 MB.
constexpr uint64_t kRequiredFreeDiskSpaceMaskInBytes = ~((512ULL << 20) - 1);
// Minimum required free disk space for ARCVM /data migration (1 GB).
constexpr uint64_t kMinimumRequiredFreeDiskSpaceInBytes = 1ULL << 30;
const uint64_t required_disk_image_size_in_bytes =
GetRequiredDiskImageSizeForArcVmDataMigrationInBytes(
android_data_size_in_bytes);
const uint64_t maximum_disk_space_overhead_in_bytes =
required_disk_image_size_in_bytes - android_data_size_in_bytes;
return (kMinimumRequiredFreeDiskSpaceInBytes +
maximum_disk_space_overhead_in_bytes) &
kRequiredFreeDiskSpaceMaskInBytes;
}
} // namespace arc