// 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.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "ash/components/arc/arc_util.h"
#include <algorithm>
#include <cstdio>
#include <optional>
#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/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/system/sys_info.h"
#include "base/time/time.h"
#include "chromeos/ash/components/dbus/concierge/concierge_client.h"
#include "chromeos/ash/components/dbus/debug_daemon/debug_daemon_client.h"
#include "chromeos/ash/components/dbus/upstart/upstart_client.h"
#include "chromeos/ash/components/dbus/vm_concierge/concierge_service.pb.h"
#include "chromeos/version/version_loader.h"
#include "components/exo/shell_surface_util.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/user_manager/user_manager.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";
// ArcUreadaheadMode 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;
}
void OnStaleArcVmStopped(
EnsureStaleArcVmAndArcVmUpstartJobsStoppedCallback callback,
std::optional<vm_tools::concierge::StopVmResponse> response) {
// Successful response is returned even when the VM is not running. See
// Service::StopVm() in platform2/vm_tools/concierge/service.cc.
if (!response.has_value() || !response->success()) {
LOG(ERROR) << "StopVm failed: "
<< (response.has_value() ? response->failure_reason()
: "No D-Bus response.");
std::move(callback).Run(false);
return;
}
std::move(callback).Run(true);
}
void OnConciergeServiceAvailable(
const std::string& user_id_hash,
EnsureStaleArcVmAndArcVmUpstartJobsStoppedCallback callback,
bool available) {
if (!available) {
LOG(ERROR) << "ConciergeService is not available";
std::move(callback).Run(false);
return;
}
vm_tools::concierge::StopVmRequest request;
request.set_name(kArcVmName);
request.set_owner_id(user_id_hash);
ash::ConciergeClient::Get()->StopVm(
request, base::BindOnce(&OnStaleArcVmStopped, std::move(callback)));
}
void OnStaleArcVmUpstartJobsStopped(
const std::string& user_id_hash,
EnsureStaleArcVmAndArcVmUpstartJobsStoppedCallback callback,
bool stopped) {
if (!stopped) {
LOG(ERROR) << "Failed to stop stale ARCVM Upstart jobs";
std::move(callback).Run(false);
return;
}
if (!ash::ConciergeClient::Get()) {
LOG(ERROR) << "ConciergeClient is not available";
std::move(callback).Run(false);
return;
}
ash::ConciergeClient::Get()->WaitForServiceToBeAvailable(base::BindOnce(
&OnConciergeServiceAvailable, user_id_hash, std::move(callback)));
}
} // 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) {
// Expected in tests and linux-chromeos that don't have /etc/lsb-release.
LOG_IF(ERROR, base::SysInfo::IsRunningOnChromeOS())
<< "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 IsArcUseDevCaches() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kArcUseDevCaches);
}
ArcUreadaheadMode GetArcUreadaheadMode(
std::string_view ureadahead_mode_switch) {
if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
ureadahead_mode_switch)) {
return ArcUreadaheadMode::READAHEAD;
}
ArcUreadaheadMode mode = ArcUreadaheadMode::READAHEAD;
const std::string value =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
ureadahead_mode_switch);
if (value == kReadahead) {
mode = ArcUreadaheadMode::READAHEAD;
} else if (value == kGenerate) {
mode = ArcUreadaheadMode::GENERATE;
} else if (value == kDisabled) {
mode = ArcUreadaheadMode::DISABLED;
} else {
LOG(FATAL) << "Invalid parameter " << value << " for "
<< ureadahead_mode_switch;
}
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 IsRobotOrOfflineDemoAccountMode() {
return user_manager::UserManager::IsInitialized() &&
user_manager::UserManager::Get()->IsLoggedInAsManagedGuestSession();
}
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;
// - Public Session users;
// kPublicAccount check is compatible with IsRobotOrOfflineDemoAccountMode()
// above because public account user is always the primary/active user of a
// user session.
if (!user->HasGaiaAccount() &&
user->GetType() != user_manager::UserType::kPublicAccount) {
VLOG(1) << "Only users with GAIA account or managed guest session users "
"are supported in ARC.";
return false;
}
return true;
}
bool IsArcOptInVerificationDisabled() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kDisableArcOptInVerification);
}
std::optional<int> GetWindowTaskId(const aura::Window* window) {
if (!window) {
return std::nullopt;
}
const std::string* window_app_id = exo::GetShellApplicationId(window);
if (!window_app_id) {
return std::nullopt;
}
return GetTaskIdFromWindowAppId(*window_app_id);
}
std::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 std::nullopt;
}
return task_id;
}
std::optional<int> GetWindowSessionId(const aura::Window* window) {
if (!window) {
return std::nullopt;
}
const std::string* window_app_id = exo::GetShellApplicationId(window);
if (!window_app_id) {
return std::nullopt;
}
return GetSessionIdFromWindowAppId(*window_app_id);
}
std::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 std::nullopt;
}
return session_id;
}
std::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();
}
}
ArcVmDataMigrationStatus GetArcVmDataMigrationStatus(PrefService* prefs) {
return static_cast<ArcVmDataMigrationStatus>(
prefs->GetInteger(prefs::kArcVmDataMigrationStatus));
}
ArcVmDataMigrationStrategy GetArcVmDataMigrationStrategy(PrefService* prefs) {
int value =
std::max(0, prefs->GetInteger(prefs::kArcVmDataMigrationStrategy));
if (value > static_cast<int>(ArcVmDataMigrationStrategy::kMaxValue)) {
LOG(ERROR) << "Unexpected value for ArcVmDataMigrationStrategy pref: "
<< value;
value = static_cast<int>(ArcVmDataMigrationStrategy::kPrompt);
}
return static_cast<ArcVmDataMigrationStrategy>(value);
}
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;
}
bool ShouldUseArcKeyMint() {
auto version = GetArcAndroidSdkVersionAsInt();
// TODO(b/308630124): Change to ">= kArcVersionT", when ready to enable
// KeyMint on ARC V+.
return version == kArcVersionT && version < kMaxArcVersion &&
base::FeatureList::IsEnabled(kSwitchToKeyMintOnT) &&
(!base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kArcBlockKeyMint) ||
base::FeatureList::IsEnabled(kSwitchToKeyMintOnTOverride));
}
bool ShouldUseArcAttestation() {
// Attesation depends on keymint.
return ShouldUseArcKeyMint() &&
base::FeatureList::IsEnabled(kEnableArcAttestation);
}
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_src_in_bytes,
uint64_t android_data_size_dest_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_dest_in_bytes);
const uint64_t maximum_disk_space_overhead_in_bytes =
required_disk_image_size_in_bytes - android_data_size_dest_in_bytes;
// Amount of additional disk space required after the migration due to
// expanded sparse files in Android /data.
uint64_t android_data_expansion_size_in_bytes = 0;
if (android_data_size_dest_in_bytes > android_data_size_src_in_bytes) {
android_data_expansion_size_in_bytes =
android_data_size_dest_in_bytes - android_data_size_src_in_bytes;
}
return (kMinimumRequiredFreeDiskSpaceInBytes +
maximum_disk_space_overhead_in_bytes +
android_data_expansion_size_in_bytes) &
kRequiredFreeDiskSpaceMaskInBytes;
}
bool IsReadOnlyPermissionsEnabled() {
return base::FeatureList::IsEnabled(arc::kEnableReadOnlyPermissions) &&
GetArcAndroidSdkVersionAsInt() >= kArcVersionT;
}
void EnsureStaleArcVmAndArcVmUpstartJobsStopped(
const std::string& user_id_hash,
EnsureStaleArcVmAndArcVmUpstartJobsStoppedCallback callback) {
// Stop stale Upstart jobs first. StopVm() is called after
// ConfigureUpstartJobs() is successfully finished.
std::deque<JobDesc> jobs;
for (const char* job : kArcVmUpstartJobsToBeStoppedOnRestart) {
jobs.emplace_back(job, UpstartOperation::JOB_STOP,
std::vector<std::string>());
}
ConfigureUpstartJobs(std::move(jobs),
base::BindOnce(&OnStaleArcVmUpstartJobsStopped,
user_id_hash, std::move(callback)));
}
bool ShouldAlwaysMountAndroidVolumesInFilesForTesting() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kArcForceMountAndroidVolumesInFiles);
}
bool ShouldDeferArcActivationUntilUserSessionStartUpTaskCompletion(
const PrefService* prefs) {
if (!base::FeatureList::IsEnabled(
kDeferArcActivationUntilUserSessionStartUpTaskCompletion)) {
return false;
}
const int max_window_size = kDeferArcActivationHistoryWindow.Get();
const int threshold = kDeferArcActivationHistoryThreshold.Get();
if (max_window_size < 0 || threshold < 0) {
LOG(ERROR) << "Unexpected negative value(s): " << max_window_size << ", "
<< threshold;
return false;
}
// Look at recent (at most) `histogram_window` sessions, and if ARC is
// activated during user session start up tasks more than or equals to
// `history_threshold` times, we'll immediately activate ARC.
// I.e., if ARC is activated during user session start up tasks less than
// `history_threshold` times, we'll defer the ARC activation until
// the user session start up task completion.
const auto& history =
prefs->GetList(prefs::kArcFirstActivationDuringUserSessionStartUpHistory);
const size_t window_size = std::min<size_t>(history.size(), max_window_size);
base::span<const base::Value> history_window(history.end() - window_size,
history.end());
return base::ranges::count(history_window, base::Value(true)) < threshold;
}
void RecordFirstActivationDuringUserSessionStartUp(PrefService* prefs,
bool value) {
if (!base::FeatureList::IsEnabled(
kDeferArcActivationUntilUserSessionStartUpTaskCompletion)) {
return;
}
const int window_size = kDeferArcActivationHistoryWindow.Get();
if (window_size < 0) {
LOG(ERROR) << "Unexpected negative window_size: " << window_size;
return;
}
ScopedListPrefUpdate update(
prefs, prefs::kArcFirstActivationDuringUserSessionStartUpHistory);
auto& history = update.Get();
// Limit the size up to the history_window.
history.Append(value);
if (history.size() >= static_cast<size_t>(window_size)) {
history.erase(history.begin(), history.end() - window_size);
}
}
} // namespace arc