| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/lock_screen_apps/lock_screen_apps.h" |
| |
| #include <memory> |
| #include <ostream> |
| |
| #include "base/check.h" |
| #include "base/check_op.h" |
| #include "base/containers/contains.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/logging.h" |
| #include "base/no_destructor.h" |
| #include "base/values.h" |
| #include "chrome/browser/apps/app_service/app_service_proxy.h" |
| #include "chrome/browser/apps/app_service/app_service_proxy_factory.h" |
| #include "chrome/browser/apps/app_service/app_service_proxy_forward.h" |
| #include "chrome/browser/ash/note_taking_helper.h" |
| #include "chrome/browser/ash/profiles/profile_helper.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/pref_names.h" |
| #include "components/keyed_service/content/browser_context_dependency_manager.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/services/app_service/public/cpp/app_registry_cache.h" |
| #include "components/services/app_service/public/cpp/app_types.h" |
| #include "components/services/app_service/public/cpp/app_update.h" |
| #include "components/services/app_service/public/cpp/intent_util.h" |
| #include "components/services/app_service/public/cpp/types_util.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/common/content_features.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/common/api/app_runtime.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/manifest_handlers/action_handlers_handler.h" |
| #include "extensions/common/mojom/api_permission_id.mojom-shared.h" |
| #include "extensions/common/permissions/permissions_data.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| namespace app_runtime = ::extensions::api::app_runtime; |
| |
| bool HasLockScreenIntentFilter( |
| const std::vector<apps::IntentFilterPtr>& filters) { |
| const auto lock_screen_intent = apps_util::CreateStartOnLockScreenIntent(); |
| for (const apps::IntentFilterPtr& filter : filters) { |
| if (lock_screen_intent->MatchFilter(filter)) |
| return true; |
| } |
| return false; |
| } |
| |
| bool IsInstalledWebApp(const std::string& app_id, Profile* profile) { |
| if (!apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile)) |
| return false; |
| auto* cache = |
| &apps::AppServiceProxyFactory::GetForProfile(profile)->AppRegistryCache(); |
| |
| bool result = false; |
| cache->ForOneApp(app_id, [&result](const apps::AppUpdate& update) { |
| if (apps_util::IsInstalled(update.Readiness()) && |
| update.AppType() == apps::AppType::kWeb) { |
| result = true; |
| } |
| }); |
| return result; |
| } |
| |
| // Whether the app's manifest indicates that the app supports use on the lock |
| // screen. |
| bool IsLockScreenCapable(Profile* profile, const std::string& app_id) { |
| if (IsInstalledWebApp(app_id, profile)) { |
| if (!base::FeatureList::IsEnabled(features::kWebLockScreenApi)) |
| return false; |
| if (!apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile)) |
| return false; |
| |
| auto& cache = apps::AppServiceProxyFactory::GetForProfile(profile) |
| ->AppRegistryCache(); |
| bool is_ready = false; |
| bool has_lock_screen_intent_filter = false; |
| cache.ForOneApp(app_id, [&has_lock_screen_intent_filter, |
| &is_ready](const apps::AppUpdate& update) { |
| if (HasLockScreenIntentFilter(update.IntentFilters())) |
| has_lock_screen_intent_filter = true; |
| is_ready = update.Readiness() == apps::Readiness::kReady; |
| }); |
| return has_lock_screen_intent_filter && is_ready; |
| } |
| |
| const extensions::Extension* chrome_app = |
| extensions::ExtensionRegistry::Get(profile)->GetExtensionById( |
| app_id, extensions::ExtensionRegistry::ENABLED); |
| if (!chrome_app) |
| return false; |
| if (!chrome_app->permissions_data()->HasAPIPermission( |
| extensions::mojom::APIPermissionID::kLockScreen)) { |
| return false; |
| } |
| return extensions::ActionHandlersInfo::HasLockScreenActionHandler( |
| chrome_app, app_runtime::ACTION_TYPE_NEW_NOTE); |
| } |
| |
| // Gets the set of app IDs that are allowed to be launched on the lock screen, |
| // if the feature is restricted using the |
| // `prefs::kNoteTakingAppsLockScreenAllowlist` preference. If the pref is not |
| // set, this method will return null (in which case the set should not be |
| // checked). |
| // Note that `prefs::kNoteTakingAppsLockScreenAllowlist` is currently only |
| // expected to be set by policy (if it's set at all). |
| std::unique_ptr<std::set<std::string>> GetAllowedLockScreenApps( |
| PrefService* prefs) { |
| const PrefService::Preference* allowed_lock_screen_apps_pref = |
| prefs->FindPreference(prefs::kNoteTakingAppsLockScreenAllowlist); |
| if (!allowed_lock_screen_apps_pref || |
| allowed_lock_screen_apps_pref->IsDefaultValue()) { |
| return nullptr; |
| } |
| |
| const base::Value* allowed_lock_screen_apps_value = |
| allowed_lock_screen_apps_pref->GetValue(); |
| |
| if (!allowed_lock_screen_apps_value || |
| !allowed_lock_screen_apps_value->is_list()) { |
| return nullptr; |
| } |
| |
| auto allowed_apps = std::make_unique<std::set<std::string>>(); |
| for (const base::Value& app_value : |
| allowed_lock_screen_apps_value->GetList()) { |
| if (!app_value.is_string()) { |
| LOG(ERROR) << "Invalid app ID value " << app_value; |
| continue; |
| } |
| |
| allowed_apps->insert(app_value.GetString()); |
| } |
| return allowed_apps; |
| } |
| |
| } // namespace |
| |
| std::ostream& operator<<(std::ostream& out, |
| const LockScreenAppSupport& support) { |
| switch (support) { |
| case LockScreenAppSupport::kNotSupported: |
| return out << "NotSupported"; |
| case LockScreenAppSupport::kNotAllowedByPolicy: |
| return out << "NotAllowedByPolicy"; |
| case LockScreenAppSupport::kSupported: |
| return out << "Supported"; |
| case LockScreenAppSupport::kEnabled: |
| return out << "Enabled"; |
| } |
| } |
| |
| // static |
| LockScreenAppSupport LockScreenApps::GetSupport(Profile* profile, |
| const std::string& app_id) { |
| LockScreenApps* lock_screen_apps = |
| LockScreenAppsFactory::GetInstance()->Get(profile); |
| if (!lock_screen_apps) |
| return LockScreenAppSupport::kNotSupported; |
| return lock_screen_apps->GetSupport(app_id); |
| } |
| |
| void LockScreenApps::UpdateAllowedLockScreenAppsList() { |
| std::unique_ptr<std::set<std::string>> allowed_apps = |
| GetAllowedLockScreenApps(profile_->GetPrefs()); |
| |
| if (allowed_apps) { |
| allowed_lock_screen_apps_state_ = AllowedAppListState::kAllowedAppsListed; |
| allowed_lock_screen_apps_by_policy_.swap(*allowed_apps); |
| } else { |
| allowed_lock_screen_apps_state_ = AllowedAppListState::kAllAppsAllowed; |
| allowed_lock_screen_apps_by_policy_.clear(); |
| } |
| } |
| |
| LockScreenAppSupport LockScreenApps::GetSupport(const std::string& app_id) { |
| if (app_id.empty()) |
| return LockScreenAppSupport::kNotSupported; |
| |
| if (!IsLockScreenCapable(profile_, app_id)) |
| return LockScreenAppSupport::kNotSupported; |
| |
| if (allowed_lock_screen_apps_state_ == AllowedAppListState::kUndetermined) |
| UpdateAllowedLockScreenAppsList(); |
| |
| if (allowed_lock_screen_apps_state_ == |
| AllowedAppListState::kAllowedAppsListed && |
| !base::Contains(allowed_lock_screen_apps_by_policy_, app_id)) { |
| return LockScreenAppSupport::kNotAllowedByPolicy; |
| } |
| |
| // Lock screen note-taking is currently enabled/disabled for all apps at once, |
| // independent of which app is preferred for note-taking. This affects the |
| // toggle shown in settings UI. Currently only the preferred app can be |
| // launched on the lock screen. |
| // TODO(crbug.com/1006642): Consider changing this so only the preferred app |
| // is reported as enabled. |
| // TODO(crbug.com/1332379): Remove this dependency on note taking code by |
| // migrating to a separate prefs entry. |
| if (profile_->GetPrefs()->GetBoolean( |
| prefs::kNoteTakingAppEnabledOnLockScreen)) |
| return LockScreenAppSupport::kEnabled; |
| |
| return LockScreenAppSupport::kSupported; |
| } |
| |
| bool LockScreenApps::SetAppEnabledOnLockScreen(const std::string& app_id, |
| bool enabled) { |
| DCHECK(!app_id.empty()); |
| |
| // Currently only the preferred note-taking app is ever enabled on the lock |
| // screen. |
| // TODO(crbug.com/1332379): Remove this dependency on note taking code by |
| // migrating to a separate prefs entry. |
| DCHECK_EQ(app_id, NoteTakingHelper::Get()->GetPreferredAppId(profile_)); |
| |
| LockScreenAppSupport current_state = GetSupport(app_id); |
| |
| if ((enabled && current_state != LockScreenAppSupport::kSupported) || |
| (!enabled && current_state != LockScreenAppSupport::kEnabled)) { |
| return false; |
| } |
| |
| // TODO(crbug.com/1332379): Migrate to a non-note-taking prefs entry. |
| profile_->GetPrefs()->SetBoolean(prefs::kNoteTakingAppEnabledOnLockScreen, |
| enabled); |
| |
| return true; |
| } |
| |
| LockScreenApps::LockScreenApps(Profile* primary_profile) |
| : profile_(primary_profile) { |
| DCHECK(LockScreenAppsFactory::IsSupportedProfile(profile_)); |
| |
| pref_change_registrar_.Init(profile_->GetPrefs()); |
| pref_change_registrar_.Add( |
| prefs::kNoteTakingAppsLockScreenAllowlist, |
| base::BindRepeating(&LockScreenApps::OnAllowedLockScreenAppsChanged, |
| base::Unretained(this))); |
| OnAllowedLockScreenAppsChanged(); |
| } |
| LockScreenApps::~LockScreenApps() = default; |
| |
| // Called when kNoteTakingAppsLockScreenAllowlist pref changes for `profile_`. |
| void LockScreenApps::OnAllowedLockScreenAppsChanged() { |
| if (allowed_lock_screen_apps_state_ == AllowedAppListState::kUndetermined) |
| return; |
| |
| std::string app_id = NoteTakingHelper::Get()->GetPreferredAppId(profile_); |
| LockScreenAppSupport lock_screen_value_before_update = GetSupport(app_id); |
| |
| UpdateAllowedLockScreenAppsList(); |
| |
| LockScreenAppSupport lock_screen_value_after_update = GetSupport(app_id); |
| |
| // Do not notify observers about preferred app change if its lock screen |
| // support status has not actually changed. |
| if (lock_screen_value_before_update != lock_screen_value_after_update) { |
| // TODO(crbug.com/1332379): Reverse this dependency by making note taking |
| // code observe this class instead. |
| NoteTakingHelper::Get()->NotifyAppUpdated(profile_, app_id); |
| } |
| } |
| |
| // --------------------------------------- |
| // LockScreenAppsFactory implementation |
| // --------------------------------------- |
| |
| // static |
| LockScreenAppsFactory* LockScreenAppsFactory::GetInstance() { |
| static base::NoDestructor<LockScreenAppsFactory> instance; |
| return instance.get(); |
| } |
| |
| // static |
| bool LockScreenAppsFactory::IsSupportedProfile(Profile* profile) { |
| if (!profile) |
| return false; |
| if (!apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile)) |
| return false; |
| if (!ProfileHelper::IsPrimaryProfile(profile)) |
| return false; |
| return true; |
| } |
| |
| LockScreenApps* LockScreenAppsFactory::Get(Profile* profile) { |
| return static_cast<LockScreenApps*>( |
| GetInstance()->GetServiceForBrowserContext(profile, /*create=*/true)); |
| } |
| |
| LockScreenAppsFactory::LockScreenAppsFactory() |
| : BrowserContextKeyedServiceFactory( |
| "LockScreenApps", |
| BrowserContextDependencyManager::GetInstance()) { |
| DependsOn(apps::AppServiceProxyFactory::GetInstance()); |
| } |
| |
| LockScreenAppsFactory::~LockScreenAppsFactory() = default; |
| |
| void LockScreenAppsFactory::RegisterProfilePrefs( |
| user_prefs::PrefRegistrySyncable* registry) { |
| registry->RegisterListPref(::prefs::kNoteTakingAppsLockScreenAllowlist); |
| registry->RegisterBooleanPref(::prefs::kNoteTakingAppEnabledOnLockScreen, |
| true); |
| } |
| |
| KeyedService* LockScreenAppsFactory::BuildServiceInstanceFor( |
| content::BrowserContext* context) const { |
| Profile* profile = Profile::FromBrowserContext(context); |
| return new LockScreenApps(profile); |
| } |
| |
| content::BrowserContext* LockScreenAppsFactory::GetBrowserContextToUse( |
| content::BrowserContext* context) const { |
| Profile* profile = Profile::FromBrowserContext(context); |
| return IsSupportedProfile(profile) ? context : nullptr; |
| } |
| |
| } // namespace ash |