| // 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 "chrome/browser/ash/arc/arc_util.h" |
| |
| #include <linux/magic.h> |
| #include <sys/statfs.h> |
| |
| #include <map> |
| #include <optional> |
| #include <set> |
| #include <string> |
| #include <utility> |
| |
| #include "ash/constants/ash_features.h" |
| #include "ash/constants/ash_switches.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/lazy_instance.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/string_util.h" |
| #include "base/system/sys_info.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/task/thread_pool.h" |
| #include "base/threading/scoped_blocking_call.h" |
| #include "chrome/browser/ash/arc/policy/arc_policy_util.h" |
| #include "chrome/browser/ash/arc/session/arc_session_manager.h" |
| #include "chrome/browser/ash/file_manager/path_util.h" |
| #include "chrome/browser/ash/guest_os/guest_os_session_tracker.h" |
| #include "chrome/browser/ash/guest_os/guest_os_session_tracker_factory.h" |
| #include "chrome/browser/ash/guest_os/guest_os_share_path.h" |
| #include "chrome/browser/ash/guest_os/guest_os_share_path_factory.h" |
| #include "chrome/browser/ash/login/configuration_keys.h" |
| #include "chrome/browser/ash/login/demo_mode/demo_session.h" |
| #include "chrome/browser/ash/login/demo_mode/demo_setup_controller.h" |
| #include "chrome/browser/ash/login/oobe_configuration.h" |
| #include "chrome/browser/ash/login/wizard_controller.h" |
| #include "chrome/browser/ash/profiles/profile_helper.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/chromeos/arc/arc_web_contents_data.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/tab_contents/tab_util.h" |
| #include "chrome/browser/ui/ash/login/login_display_host.h" |
| #include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h" |
| #include "chrome/browser/ui/simple_message_box.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "chromeos/ash/components/browser_context_helper/browser_context_helper.h" |
| #include "chromeos/ash/components/demo_mode/utils/demo_session_utils.h" |
| #include "chromeos/ash/components/install_attributes/install_attributes.h" |
| #include "chromeos/ash/components/settings/cros_settings.h" |
| #include "chromeos/ash/experiences/arc/arc_features.h" |
| #include "chromeos/ash/experiences/arc/arc_prefs.h" |
| #include "chromeos/ash/experiences/arc/arc_util.h" |
| #include "chromeos/components/mgs/managed_guest_session_utils.h" |
| #include "components/embedder_support/user_agent_utils.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/user_manager/known_user.h" |
| #include "components/user_manager/user.h" |
| #include "components/user_manager/user_manager.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/site_instance.h" |
| #include "content/public/browser/web_contents.h" |
| #include "ui/aura/window.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "url/gurl.h" |
| |
| // Enable VLOG level 1. |
| #undef ENABLED_VLOG_LEVEL |
| #define ENABLED_VLOG_LEVEL 1 |
| |
| namespace arc { |
| |
| namespace { |
| |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. |
| // Status of ARC based on device affiliation. |
| enum class DeviceAffiliationBasedArcStatus { |
| // ARC allowed on affiliated device |
| kAllowedOnAffiliatedDevice, |
| |
| // ARC allowed on unaffiliated device |
| kAllowedOnUnaffiliatedDevice, |
| |
| // ARC not allowed on unaffiliated device |
| kDisallowedOnUnaffiliatedDevice, |
| |
| kMaxValue = kDisallowedOnUnaffiliatedDevice, |
| }; |
| |
| enum class ArcStatus { |
| // ARC disallowed for testing |
| kDisallowedForTesting, |
| |
| // ARC is not available |
| kNotAvailable, |
| |
| // Non-primary users are not supported in ARC |
| kNonPrimaryUsersNotSupported, |
| |
| // ARC is disabled by flag for managed user |
| kDisabledByFlagForManagedUser, |
| |
| // ARC is not allowed for the user |
| kDisallowedForUser, |
| |
| // Device admin disallowed ARC for unaffiliated user |
| kDisallowedByDevicePolicyRestriction, |
| |
| // ARC disallowed for unaffiliated users |
| kDisallowedByUserPolicyRestriction, |
| |
| // ARC allowed on affiliated device |
| kAllowedOnAffiliatedDevice, |
| |
| // ARC allowed on unaffilated device |
| kAllowedOnUnaffiliatedDevice, |
| |
| // ARC allowed |
| kAllowed, |
| }; |
| |
| // Contains map of profile to check result of ARC allowed. Contains true if ARC |
| // allowed check was performed and ARC is allowed. If map does not contain |
| // a value then this means that check has not been performed yet. |
| base::LazyInstance<std::map<const Profile*, bool>>::DestructorAtExit |
| g_profile_status_check = LAZY_INSTANCE_INITIALIZER; |
| |
| // Let IsAllowedForProfile() return "false" for any profile. |
| bool g_disallow_for_testing = false; |
| |
| // Let IsArcBlockedDueToIncompatibleFileSystem() return the specified value |
| // during test runs. Doesn't affect public session. |
| bool g_arc_blocked_due_to_incompatible_filesystem_for_testing = false; |
| |
| // Indicates whether the ARCVM DLC image is available on the device with the |
| // arcvm_dlc USE flag. This value must be updated before calling |
| // IsArcAllowedForProfile() to maintain consistency. |
| // |
| // 1. Regular login: The check is performed in UserSessionManager when the user |
| // logs in. |
| // 2. Chrome restart: On ash-chrome restart, the session bypasses |
| // UserSessionManager. In this case, the check is done in |
| // ArcServiceLauncher, blocking the main thread to ensure the check |
| // completes before calling IsArcAllowedForProfile(). |
| std::optional<bool> g_is_arcvm_dlc_image_available; |
| |
| // TODO(kinaba): Temporary workaround for crbug.com/729034. |
| // |
| // Some type of accounts don't have user prefs. As a short-term workaround, |
| // store the compatibility info from them on memory, ignoring the defect that |
| // it cannot survive browser crash and restart. |
| // |
| // This will be removed once the forced migration for ARC Kiosk user is |
| // implemented. After it's done such types of accounts cannot even sign-in |
| // with incompatible filesystem. Hence it'll be safe to always regard compatible |
| // for them then. |
| base::LazyInstance<std::set<AccountId>>::DestructorAtExit |
| g_known_compatible_users = LAZY_INSTANCE_INITIALIZER; |
| |
| // Returns whether ARC can run on the filesystem mounted at |path|. |
| // This function should run only on threads where IO operations are allowed. |
| bool IsArcCompatibleFilesystem(const base::FilePath& path) { |
| base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, |
| base::BlockingType::MAY_BLOCK); |
| |
| // If it can be verified it is not on ecryptfs, then it is ok. |
| struct statfs statfs_buf; |
| if (statfs(path.value().c_str(), &statfs_buf) < 0) { |
| VPLOG(1) << "statfs failed"; |
| return false; |
| } |
| return statfs_buf.f_type != ECRYPTFS_SUPER_MAGIC; |
| } |
| |
| FileSystemCompatibilityState GetFileSystemCompatibilityPref( |
| const AccountId& account_id) { |
| user_manager::KnownUser known_user(g_browser_process->local_state()); |
| if (auto pref = known_user.FindIntPath(account_id, |
| prefs::kArcCompatibleFilesystemChosen); |
| pref) { |
| return static_cast<FileSystemCompatibilityState>(pref.value()); |
| } |
| VLOG(1) << "arc.compatible_filesystem.chosen not set. Assuming incompatible."; |
| return kFileSystemIncompatible; |
| } |
| |
| // Similar to GetFileSystemCompatibilityPref, but when the pref is not found, |
| // it optimistically assumes that the file system is compatible. This is for |
| // the mitigation of issues like b/327969092 that unexpectedly clears the pref |
| // during the initial sign-in. |
| FileSystemCompatibilityState GetFileSystemCompatibilityPrefOptimistic( |
| const AccountId& account_id) { |
| user_manager::KnownUser known_user(g_browser_process->local_state()); |
| if (auto pref = known_user.FindIntPath(account_id, |
| prefs::kArcCompatibleFilesystemChosen); |
| pref) { |
| return static_cast<FileSystemCompatibilityState>(pref.value()); |
| } |
| VLOG(1) << "arc.compatible_filesystem.chosen not set. Assuming compatible."; |
| return kFileSystemCompatible; |
| } |
| |
| // Stores the result of IsArcCompatibleFilesystem posted back from the blocking |
| // task runner. |
| void StoreCompatibilityCheckResult(const AccountId& account_id, |
| base::OnceClosure callback, |
| bool is_compatible) { |
| if (is_compatible) { |
| user_manager::KnownUser known_user(g_browser_process->local_state()); |
| known_user.SetIntegerPref(account_id, prefs::kArcCompatibleFilesystemChosen, |
| kFileSystemCompatible); |
| |
| // TODO(kinaba): Remove this code for accounts without user prefs. |
| // See the comment for |g_known_compatible_users| for the detail. |
| if (GetFileSystemCompatibilityPref(account_id) != kFileSystemCompatible) { |
| g_known_compatible_users.Get().insert(account_id); |
| } |
| } |
| std::move(callback).Run(); |
| } |
| |
| // Returns whether the ARCVM DLC image is already installed on the reven device. |
| // This function performs a blocking IO operation and should only be called on |
| // threads that allow blocking IO, such as worker threads or IO threads. It |
| // must not be called on the UI thread. |
| bool IsArcVmDlcImageExist() { |
| DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, |
| base::BlockingType::MAY_BLOCK); |
| const char arc_vm_dlc_image_path[] = "/opt/google/vms/android/system.raw.img"; |
| return base::PathExists(base::FilePath(arc_vm_dlc_image_path)); |
| } |
| |
| // Stores the result of IsArcVmDlcImageExist posted back from the blocking |
| // task runner. |
| void StoreArcVmDlcImageCheckResult(base::OnceClosure callback, |
| bool is_available) { |
| g_is_arcvm_dlc_image_available = is_available; |
| std::move(callback).Run(); |
| } |
| |
| bool IsUnaffiliatedArcAllowed() { |
| bool arc_allowed; |
| ArcSessionManager* arc_session_manager = ArcSessionManager::Get(); |
| if (arc_session_manager) { |
| switch (arc_session_manager->state()) { |
| case ArcSessionManager::State::NOT_INITIALIZED: |
| case ArcSessionManager::State::STOPPED: |
| // Apply logic below |
| break; |
| case ArcSessionManager::State::CHECKING_REQUIREMENTS: |
| case ArcSessionManager::State::REMOVING_DATA_DIR: |
| case ArcSessionManager::State::CHECKING_DATA_MIGRATION_NECESSITY: |
| case ArcSessionManager::State::READY: |
| case ArcSessionManager::State::ACTIVE: |
| case ArcSessionManager::State::STOPPING: |
| // Never forbid unaffiliated ARC while ARC is running |
| return true; |
| } |
| } |
| if (ash::CrosSettings::Get()->GetBoolean(ash::kUnaffiliatedArcAllowed, |
| &arc_allowed)) { |
| return arc_allowed; |
| } |
| // If device policy is not set, allow ARC. |
| return true; |
| } |
| |
| ArcStatus GetArcStatusForProfile(const Profile* profile, |
| bool should_report_reason) { |
| if (g_disallow_for_testing) { |
| VLOG_IF(1, should_report_reason) << "ARC is disallowed for testing."; |
| return ArcStatus::kDisallowedForTesting; |
| } |
| |
| if (!IsArcAvailable()) { |
| VLOG_IF(1, should_report_reason) << "ARC is not available."; |
| return ArcStatus::kNotAvailable; |
| } |
| |
| if (ash::switches::IsRevenBranding()) { |
| CHECK(g_is_arcvm_dlc_image_available.has_value()); |
| |
| if (!g_is_arcvm_dlc_image_available.value()) { |
| VLOG_IF(1, should_report_reason) |
| << "ARC unavailable on reven board due to no arcvm dlc images."; |
| return ArcStatus::kNotAvailable; |
| } |
| |
| if (!policy_util::IsAccountManaged(profile) || |
| !ash::InstallAttributes::Get()->IsEnterpriseManaged()) { |
| VLOG_IF(1, should_report_reason) << "ARC unavailable on reven board due " |
| "to unmanaged device or account."; |
| return ArcStatus::kNotAvailable; |
| } |
| } |
| |
| if (!ash::ProfileHelper::IsPrimaryProfile(profile)) { |
| VLOG_IF(1, should_report_reason) |
| << "Non-primary users are not supported in ARC."; |
| return ArcStatus::kNonPrimaryUsersNotSupported; |
| } |
| |
| if (policy_util::IsArcDisabledForEnterprise() && |
| policy_util::IsAccountManaged(profile)) { |
| VLOG_IF(1, should_report_reason) |
| << "ARC is disabled by flag for managed users."; |
| return ArcStatus::kDisabledByFlagForManagedUser; |
| } |
| |
| // Play Store requires an appropriate application install mechanism. Normal |
| // users do this through GAIA, but Kiosk users use a different application |
| // install mechanism. ARC is not allowed otherwise (e.g. in public sessions, |
| // as described in crbug.com/605545). |
| const user_manager::User* user = |
| ash::ProfileHelper::Get()->GetUserByProfile(profile); |
| if (!IsArcAllowedForUser(user)) { |
| VLOG_IF(1, should_report_reason) << "ARC is not allowed for the user."; |
| return ArcStatus::kDisallowedForUser; |
| } |
| |
| if (!user->IsAffiliated() && !IsUnaffiliatedArcAllowed()) { |
| VLOG_IF(1, should_report_reason) |
| << "Device admin disallowed ARC for unaffiliated users."; |
| return ArcStatus::kDisallowedByDevicePolicyRestriction; |
| } |
| |
| if (!user->IsAffiliated() && |
| !profile->GetPrefs()->GetBoolean(prefs::kUnaffiliatedDeviceArcAllowed) && |
| policy_util::IsAccountManaged(profile)) { |
| VLOG_IF(1, should_report_reason) << "ARC disallowed for unaffiliated users"; |
| return arc::ArcStatus::kDisallowedByUserPolicyRestriction; |
| } |
| |
| // Please add any condition that disallows ARC above this check. |
| const bool is_arc_allowed_on_unaffiliated_devices = |
| profile->GetPrefs()->GetBoolean(prefs::kUnaffiliatedDeviceArcAllowed); |
| if (user->IsAffiliated() && !is_arc_allowed_on_unaffiliated_devices) { |
| return ArcStatus::kAllowedOnAffiliatedDevice; |
| } |
| if (!user->IsAffiliated() && is_arc_allowed_on_unaffiliated_devices) { |
| return ArcStatus::kAllowedOnUnaffiliatedDevice; |
| } |
| |
| return ArcStatus::kAllowed; |
| } |
| |
| bool IsArcAllowedForProfileInternal(const Profile* profile, |
| bool should_report_reason) { |
| const ArcStatus status = |
| GetArcStatusForProfile(profile, should_report_reason); |
| return status == ArcStatus::kAllowed || |
| status == ArcStatus::kAllowedOnAffiliatedDevice || |
| status == ArcStatus::kAllowedOnUnaffiliatedDevice; |
| } |
| |
| void ShowContactAdminDialog() { |
| chrome::ShowWarningMessageBoxAsync( |
| nullptr, l10n_util::GetStringUTF16(IDS_ARC_OPT_IN_CONTACT_ADMIN_TITLE), |
| l10n_util::GetStringUTF16(IDS_ARC_OPT_IN_CONTACT_ADMIN_CONTEXT)); |
| } |
| |
| void SharePathIfRequired(ConvertToContentUrlsAndShareCallback callback, |
| const std::vector<GURL>& content_urls, |
| const std::vector<base::FilePath>& paths_to_share) { |
| DCHECK(arc::IsArcVmEnabled() || paths_to_share.empty()); |
| std::vector<base::FilePath> path_list; |
| Profile* const profile = ProfileManager::GetPrimaryUserProfile(); |
| DCHECK(profile); |
| for (const auto& path : paths_to_share) { |
| if (!guest_os::GuestOsSharePathFactory::GetForProfile(profile) |
| ->IsPathShared(kArcVmName, path)) { |
| path_list.push_back(path); |
| } |
| } |
| if (path_list.empty()) { |
| std::move(callback).Run(content_urls); |
| return; |
| } |
| |
| const auto& vm_info = |
| guest_os::GuestOsSessionTrackerFactory::GetForProfile(profile)->GetVmInfo( |
| kArcVmName); |
| if (!vm_info) { |
| LOG(WARNING) << "ARCVM not running, cannot share paths"; |
| std::move(callback).Run(std::vector<GURL>()); |
| return; |
| } |
| guest_os::GuestOsSharePathFactory::GetForProfile(profile)->SharePaths( |
| kArcVmName, vm_info->seneschal_server_handle(), path_list, |
| base::BindOnce( |
| [](ConvertToContentUrlsAndShareCallback callback, |
| const std::vector<GURL>& content_urls, bool success, |
| const std::string& failure_reason) { |
| if (success) { |
| std::move(callback).Run(content_urls); |
| } else { |
| LOG(ERROR) << "Error sharing ARC content URLs: " |
| << failure_reason; |
| std::move(callback).Run(std::vector<GURL>()); |
| } |
| }, |
| std::move(callback), content_urls)); |
| } |
| |
| } // namespace |
| |
| void RecordArcStatusBasedOnDeviceAffiliationUMA(Profile* profile) { |
| if (!policy_util::IsAccountManaged(profile) || |
| !profile->GetPrefs()->GetBoolean(prefs::kArcEnabled)) { |
| return; |
| } |
| |
| switch (GetArcStatusForProfile(profile, /*should_report_reason=*/false)) { |
| case arc::ArcStatus::kAllowedOnAffiliatedDevice: |
| base::UmaHistogramEnumeration( |
| "Arc.Provisioning.DeviceAffiliationAction", |
| arc::DeviceAffiliationBasedArcStatus::kAllowedOnAffiliatedDevice); |
| break; |
| case arc::ArcStatus::kAllowedOnUnaffiliatedDevice: |
| base::UmaHistogramEnumeration( |
| "Arc.Provisioning.DeviceAffiliationAction", |
| arc::DeviceAffiliationBasedArcStatus::kAllowedOnUnaffiliatedDevice); |
| break; |
| case arc::ArcStatus::kDisallowedByUserPolicyRestriction: |
| base::UmaHistogramEnumeration("Arc.Provisioning.DeviceAffiliationAction", |
| arc::DeviceAffiliationBasedArcStatus:: |
| kDisallowedOnUnaffiliatedDevice); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| bool IsRealUserProfile(const Profile* profile) { |
| // Return false for signin, lock screen and incognito profiles. |
| return profile && ash::ProfileHelper::IsUserProfile(profile) && |
| !profile->IsOffTheRecord(); |
| } |
| |
| bool IsArcAllowedForProfile(const Profile* profile) { |
| if (!IsRealUserProfile(profile)) { |
| return false; |
| } |
| |
| auto it = g_profile_status_check.Get().find(profile); |
| |
| const bool first_check = it == g_profile_status_check.Get().end(); |
| const bool result = |
| IsArcAllowedForProfileInternal(profile, first_check /* report_reason */); |
| |
| if (first_check) { |
| g_profile_status_check.Get()[profile] = result; |
| return result; |
| } |
| |
| // This is next check. We should be persistent and report the same result. |
| if (result != it->second) { |
| NOTREACHED() << "ARC allowed was changed for the current user session " |
| << "and profile " << profile->GetPath().MaybeAsASCII() |
| << ". This may lead to unexpected behavior. ARC allowed is" |
| << " forced to " << it->second; |
| } |
| return it->second; |
| } |
| |
| bool IsArcProvisioned(const Profile* profile) { |
| return profile && profile->GetPrefs()->HasPrefPath(prefs::kArcSignedIn) && |
| profile->GetPrefs()->GetBoolean(prefs::kArcSignedIn); |
| } |
| |
| void ResetArcAllowedCheckForTesting(const Profile* profile) { |
| g_profile_status_check.Get().erase(profile); |
| } |
| |
| void ClearArcAllowedCheckForTesting() { |
| g_profile_status_check.Get().clear(); |
| } |
| |
| bool IsArcBlockedDueToIncompatibleFileSystem(const Profile* profile) { |
| const user_manager::User* user = |
| ash::ProfileHelper::Get()->GetUserByProfile(profile); |
| |
| // Unblock Arc for public accounts as they only have ext4 and |
| // for ARC kiosk as migration to ext4 should always be triggered. |
| // Without this check it fails to start after browser crash as |
| // compatibility info is stored in RAM. |
| // In the other kiosk types we do not/should not start ARCVM or allow android |
| // apps to run hence this method should evaluate the actual file system and |
| // not be bypassed here. |
| if (user && (user->GetType() == user_manager::UserType::kPublicAccount || |
| user->GetType() == user_manager::UserType::kKioskArcvmApp)) { |
| return false; |
| } |
| |
| // Test runs on Linux workstation does not have expected /etc/lsb-release |
| // field nor profile creation step. Hence it returns a dummy test value. |
| if (!base::SysInfo::IsRunningOnChromeOS()) { |
| return g_arc_blocked_due_to_incompatible_filesystem_for_testing; |
| } |
| |
| // Conducts the actual check, only when running on a real Chrome OS device. |
| return !IsArcCompatibleFileSystemUsedForUser(user); |
| } |
| |
| void SetArcBlockedDueToIncompatibleFileSystemForTesting(bool block) { |
| g_arc_blocked_due_to_incompatible_filesystem_for_testing = block; |
| } |
| |
| void SetArcvmDlcImageStatusForTesting(std::optional<bool> availability) { |
| g_is_arcvm_dlc_image_available = availability; |
| } |
| |
| bool IsArcCompatibleFileSystemUsedForUser(const user_manager::User* user) { |
| // Returns false for profiles not associated with users (like sign-in profile) |
| if (!user) { |
| return false; |
| } |
| |
| // ash::UserSessionManager does the actual file system check and stores |
| // the result to prefs, so that it survives crash-restart. |
| FileSystemCompatibilityState filesystem_compatibility = |
| GetFileSystemCompatibilityPrefOptimistic(user->GetAccountId()); |
| const bool is_filesystem_compatible = |
| filesystem_compatibility != kFileSystemIncompatible || |
| g_known_compatible_users.Get().count(user->GetAccountId()) != 0; |
| |
| // To run ARC we want to make sure the underlying file system is compatible |
| // with ARC. |
| if (!is_filesystem_compatible) { |
| VLOG(1) |
| << "ARC is not supported since the user hasn't migrated to dircrypto."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void DisallowArcForTesting() { |
| g_disallow_for_testing = true; |
| g_profile_status_check.Get().clear(); |
| } |
| |
| bool IsArcPlayStoreEnabledForProfile(const Profile* profile) { |
| return IsArcAllowedForProfile(profile) && |
| profile->GetPrefs()->GetBoolean(prefs::kArcEnabled); |
| } |
| |
| bool IsArcPlayStoreEnabledPreferenceManagedForProfile(const Profile* profile) { |
| if (!IsArcAllowedForProfile(profile)) { |
| LOG(DFATAL) << "ARC is not allowed for profile"; |
| return false; |
| } |
| return profile->GetPrefs()->IsManagedPreference(prefs::kArcEnabled); |
| } |
| |
| bool SetArcPlayStoreEnabledForProfile(Profile* profile, bool enabled) { |
| DCHECK(IsArcAllowedForProfile(profile)); |
| if (IsArcPlayStoreEnabledPreferenceManagedForProfile(profile)) { |
| if (enabled && !IsArcPlayStoreEnabledForProfile(profile)) { |
| LOG(WARNING) << "Attempt to enable disabled by policy ARC."; |
| if (ash::switches::IsTabletFormFactor()) { |
| VLOG(1) << "Showing contact admin dialog managed user of tablet form " |
| "factor devices."; |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&ShowContactAdminDialog)); |
| } |
| return false; |
| } |
| VLOG(1) << "Google-Play-Store-enabled pref is managed. Request to " |
| << (enabled ? "enable" : "disable") << " Play Store is not stored"; |
| // Need update ARC session manager manually for managed case in order to |
| // keep its state up to date, otherwise it may stuck with enabling |
| // request. |
| // TODO(khmel): Consider finding the better way handling this. |
| ArcSessionManager* arc_session_manager = ArcSessionManager::Get(); |
| // |arc_session_manager| can be nullptr in unit_tests. |
| if (!arc_session_manager) { |
| return false; |
| } |
| if (enabled) { |
| arc_session_manager->AllowActivation( |
| ArcSessionManager::AllowActivationReason::kUserEnableAction); |
| arc_session_manager->RequestEnable(); |
| } else { |
| arc_session_manager->RequestDisableWithArcDataRemoval(); |
| } |
| |
| return true; |
| } |
| profile->GetPrefs()->SetBoolean(prefs::kArcEnabled, enabled); |
| return true; |
| } |
| |
| bool AreArcAllOptInPreferencesIgnorableForProfile(const Profile* profile) { |
| // The preferences are ignorable iff both backup&restore and location services |
| // are set by policy. |
| const PrefService* prefs = profile->GetPrefs(); |
| |
| if (ash::features::IsCrosPrivacyHubLocationEnabled()) { |
| // When PH is enabled, location toggle is no longer ARC specific (applies to |
| // entire ChromeOS); |
| return prefs->IsManagedPreference(prefs::kArcBackupRestoreEnabled); |
| } else { |
| return prefs->IsManagedPreference(prefs::kArcBackupRestoreEnabled) && |
| prefs->IsManagedPreference(prefs::kArcLocationServiceEnabled); |
| } |
| } |
| |
| bool IsArcOobeOptInActive() { |
| // No OOBE is expected in case Play Store is not available. |
| if (!IsPlayStoreAvailable()) { |
| return false; |
| } |
| |
| // Check if Chrome OS OOBE flow is currently showing. |
| // TODO(b/65861628): Redesign the OptIn flow since there is no longer reason |
| // to have two different OptIn flows. |
| if (!ash::LoginDisplayHost::default_host()) { |
| return false; |
| } |
| |
| // ARC OOBE opt-in will only be active if the user did not complete the |
| // onboarding flow yet. The OnboardingCompletedVersion preference will only be |
| // saved after the onboarding flow is completed. |
| AccountId account_id = |
| user_manager::UserManager::Get()->GetActiveUser()->GetAccountId(); |
| user_manager::KnownUser known_user(g_browser_process->local_state()); |
| return !known_user.GetOnboardingCompletedVersion(account_id).has_value(); |
| } |
| |
| bool IsArcOobeOptInConfigurationBased() { |
| // Ignore if not applicable. |
| if (!IsArcOobeOptInActive()) { |
| return false; |
| } |
| // Check that configuration exist. |
| auto* oobe_configuration = ash::OobeConfiguration::Get(); |
| if (!oobe_configuration) { |
| return false; |
| } |
| if (!oobe_configuration->CheckCompleted()) { |
| return false; |
| } |
| // Check configuration value that triggers automatic ARC TOS acceptance. |
| auto& configuration = oobe_configuration->configuration(); |
| auto auto_accept = |
| configuration.FindBool(ash::configuration::kArcTosAutoAccept); |
| if (!auto_accept) { |
| return false; |
| } |
| return *auto_accept; |
| } |
| |
| bool IsArcTermsOfServiceNegotiationNeeded(const Profile* profile) { |
| DCHECK(profile); |
| // Don't show in session ARC OptIn dialog for managed user. |
| // For more info see crbug/950013. |
| // Skip to show UI asking users to set up ARC OptIn preferences, if all of |
| // them are managed by the admin policy. Note that the ToS agreement is anyway |
| // not shown in the case of the managed ARC. |
| if (ShouldStartArcSilentlyForManagedProfile(profile) && |
| !ShouldShowOptInForTesting()) { |
| VLOG(1) << "Skip ARC Terms of Service negotiation for managed user. " |
| << "Don't record B&R and GLS if admin leave it as user to decide " |
| << "and user skips the opt-in dialog."; |
| return false; |
| } |
| |
| // If it is marked that the Terms of service is accepted already, |
| // just skip the negotiation with user, and start Android management |
| // check directly. |
| // This happens, e.g., when a user accepted the Terms of service on Opt-in |
| // flow, but logged out before ARC sign in procedure was done. Then, logs |
| // in again. |
| if (profile->GetPrefs()->GetBoolean(prefs::kArcTermsAccepted)) { |
| VLOG(1) << "The user already accepts ARC Terms of Service."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool IsArcTermsOfServiceOobeNegotiationNeeded() { |
| if (!IsPlayStoreAvailable()) { |
| VLOG(1) << "Skip ARC Terms of Service screen because Play Store is not " |
| "available on the device."; |
| return false; |
| } |
| |
| // Demo mode setup flow runs before user is created, therefore this condition |
| // needs to be checked before any user related ones. |
| if (IsArcDemoModeSetupFlow()) { |
| return true; |
| } |
| |
| if (!user_manager::UserManager::Get()->IsUserLoggedIn()) { |
| VLOG(1) << "Skip ARC Terms of Service screen because user is not " |
| << "logged in."; |
| return false; |
| } |
| |
| const Profile* profile = ProfileManager::GetActiveUserProfile(); |
| if (!IsArcAllowedForProfile(profile)) { |
| VLOG(1) << "Skip ARC Terms of Service screen because ARC is not allowed."; |
| return false; |
| } |
| |
| if (profile->GetPrefs()->IsManagedPreference(prefs::kArcEnabled) && |
| !profile->GetPrefs()->GetBoolean(prefs::kArcEnabled)) { |
| VLOG(1) << "Skip ARC Terms of Service screen because ARC is disabled."; |
| return false; |
| } |
| |
| if (!IsArcTermsOfServiceNegotiationNeeded(profile)) { |
| VLOG(1) << "Skip ARC Terms of Service screen because it is already " |
| "accepted or fully controlled by policy."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool IsArcStatsReportingEnabled() { |
| // Managed guest session users never saw the consent for stats reporting even |
| // if the admin forced the pref by a policy. |
| if (chromeos::IsManagedGuestSession()) { |
| return false; |
| } |
| |
| bool pref = false; |
| ash::CrosSettings::Get()->GetBoolean(ash::kStatsReportingPref, &pref); |
| return pref; |
| } |
| |
| bool IsArcDemoModeSetupFlow() { |
| return ash::DemoSetupController::IsOobeDemoSetupFlowInProgress(); |
| } |
| |
| void UpdateArcFileSystemCompatibilityPrefIfNeeded( |
| const AccountId& account_id, |
| const base::FilePath& profile_path, |
| base::OnceClosure callback) { |
| DCHECK(callback); |
| |
| // If ARC is not available, skip the check. |
| // This shortcut is just for marginally improving the log-in performance on |
| // old devices without ARC. We can always safely remove the following 4 lines |
| // without changing any functionality when, say, the code clarity becomes |
| // more important in the future. |
| if (!IsArcAvailable() && !IsArcvmKioskAvailable()) { |
| std::move(callback).Run(); |
| return; |
| } |
| |
| // If the compatibility has been already confirmed, skip the check. |
| if (GetFileSystemCompatibilityPref(account_id) != kFileSystemIncompatible) { |
| std::move(callback).Run(); |
| return; |
| } |
| |
| // Otherwise, check the underlying filesystem. |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, |
| {base::MayBlock(), base::TaskPriority::USER_BLOCKING, |
| base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, |
| base::BindOnce(&IsArcCompatibleFilesystem, profile_path), |
| base::BindOnce(&StoreCompatibilityCheckResult, account_id, |
| std::move(callback))); |
| } |
| |
| void CheckArcVmDlcImageExist(base::OnceClosure callback) { |
| if (!arc::IsArcVmDlcEnabled()) { |
| std::move(callback).Run(); |
| return; |
| } |
| |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, |
| {base::MayBlock(), base::TaskPriority::USER_BLOCKING, |
| base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, |
| base::BindOnce(&IsArcVmDlcImageExist), |
| base::BindOnce(&StoreArcVmDlcImageCheckResult, std::move(callback))); |
| } |
| |
| void SetArcvmDlcImageStatus(bool availability) { |
| g_is_arcvm_dlc_image_available = availability; |
| } |
| |
| ArcManagementTransition GetManagementTransition(const Profile* profile) { |
| DCHECK(profile); |
| DCHECK(profile->GetPrefs()); |
| |
| const ArcManagementTransition management_transition = |
| static_cast<ArcManagementTransition>( |
| profile->GetPrefs()->GetInteger(prefs::kArcManagementTransition)); |
| return management_transition; |
| } |
| |
| bool IsPlayStoreAvailable() { |
| if (ShouldArcAlwaysStartWithNoPlayStore()) { |
| return false; |
| } |
| |
| if (!IsRobotOrOfflineDemoAccountMode()) { |
| return true; |
| } |
| |
| // Demo Mode is the only public session scenario that can launch Play. |
| return ash::demo_mode::IsDeviceInDemoMode(); |
| } |
| |
| bool ShouldStartArcSilentlyForManagedProfile(const Profile* profile) { |
| return IsArcPlayStoreEnabledPreferenceManagedForProfile(profile) && |
| (AreArcAllOptInPreferencesIgnorableForProfile(profile) || |
| !IsArcOobeOptInActive()); |
| } |
| |
| aura::Window* GetArcWindow(int32_t task_id) { |
| for (auto* window : ChromeShelfController::instance()->GetArcWindows()) { |
| if (arc::GetWindowTaskId(window) == task_id) { |
| return window; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| std::unique_ptr<content::WebContents> CreateArcCustomTabWebContents( |
| Profile* profile, |
| const GURL& url) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| scoped_refptr<content::SiteInstance> site_instance = |
| tab_util::GetSiteInstanceForNewTab(profile, url); |
| content::WebContents::CreateParams create_params(profile, site_instance); |
| std::unique_ptr<content::WebContents> web_contents = |
| content::WebContents::Create(create_params); |
| |
| // Use the same version number as browser_commands.cc |
| // TODO(hashimoto): Get the actual Android version from the container; |
| // also for |structured_ua.platform_version| below. |
| constexpr char kOsOverrideForTabletSite[] = "Linux; Android 9; Chrome tablet"; |
| // Override the user agent to request mobile version web sites. |
| const std::string product = embedder_support::GetProductAndVersion(); |
| blink::UserAgentOverride ua_override; |
| ua_override.ua_string_override = |
| embedder_support::BuildUserAgentFromOSAndProduct(kOsOverrideForTabletSite, |
| product); |
| |
| ua_override.ua_metadata_override = embedder_support::GetUserAgentMetadata(); |
| ua_override.ua_metadata_override->platform = "Android"; |
| ua_override.ua_metadata_override->platform_version = "9"; |
| ua_override.ua_metadata_override->model = "Chrome tablet"; |
| ua_override.ua_metadata_override->mobile = false; |
| |
| web_contents->SetUserAgentOverride(ua_override, |
| false /*override_in_new_tabs=*/); |
| |
| content::NavigationController::LoadURLParams load_url_params(url); |
| load_url_params.source_site_instance = site_instance; |
| load_url_params.override_user_agent = |
| content::NavigationController::UA_OVERRIDE_TRUE; |
| web_contents->GetController().LoadURLWithParams(load_url_params); |
| |
| // Add a flag to remember this tab originated in the ARC context. |
| web_contents->SetUserData( |
| &arc::ArcWebContentsData::kArcTransitionFlag, |
| std::make_unique<arc::ArcWebContentsData>(web_contents.get())); |
| |
| return web_contents; |
| } |
| |
| std::string GetHistogramNameByUserType(const std::string& base_name, |
| const Profile* profile) { |
| if (profile == nullptr) { |
| profile = ProfileManager::GetPrimaryUserProfile(); |
| } |
| if (IsRobotOrOfflineDemoAccountMode()) { |
| auto* demo_session = ash::DemoSession::Get(); |
| if (demo_session && demo_session->started()) { |
| return base_name + ".DemoMode"; |
| } |
| return base_name + ".RobotAccount"; |
| } |
| if (profile->IsChild()) { |
| return base_name + ".Child"; |
| } |
| |
| return base_name + |
| (policy_util::IsAccountManaged(profile) ? ".Managed" : ".Unmanaged"); |
| } |
| |
| std::string GetHistogramNameByUserTypeForPrimaryProfile( |
| const std::string& base_name) { |
| return GetHistogramNameByUserType(base_name, /*profile=*/nullptr); |
| } |
| |
| void ConvertToContentUrlsAndShare( |
| Profile* profile, |
| const std::vector<storage::FileSystemURL>& file_system_urls, |
| ConvertToContentUrlsAndShareCallback callback) { |
| file_manager::util::ConvertToContentUrls( |
| profile, std::move(file_system_urls), |
| base::BindOnce(&SharePathIfRequired, std::move(callback))); |
| } |
| |
| } // namespace arc |