diff --git a/chrome/browser/chromeos/lock_screen_apps/app_manager_impl.cc b/chrome/browser/chromeos/lock_screen_apps/app_manager_impl.cc index 6f2a7c98668e..62afd988046f 100644 --- a/chrome/browser/chromeos/lock_screen_apps/app_manager_impl.cc +++ b/chrome/browser/chromeos/lock_screen_apps/app_manager_impl.cc @@ -113,7 +113,9 @@ void InstallExtensionCopy( } // namespace AppManagerImpl::AppManagerImpl() - : extensions_observer_(this), weak_ptr_factory_(this) {} + : extensions_observer_(this), + note_taking_helper_observer_(this), + weak_ptr_factory_(this) {} AppManagerImpl::~AppManagerImpl() = default; @@ -138,15 +140,7 @@ void AppManagerImpl::Initialize(Profile* primary_profile, lock_screen_profile_ = lock_screen_profile; state_ = State::kInactive; - pref_change_registrar_.Init(primary_profile->GetPrefs()); - pref_change_registrar_.Add( - prefs::kNoteTakingAppId, - base::Bind(&AppManagerImpl::OnNoteTakingExtensionChanged, - base::Unretained(this))); - pref_change_registrar_.Add( - prefs::kNoteTakingAppEnabledOnLockScreen, - base::Bind(&AppManagerImpl::OnNoteTakingExtensionChanged, - base::Unretained(this))); + note_taking_helper_observer_.Add(chromeos::NoteTakingHelper::Get()); } void AppManagerImpl::Start(const base::Closure& note_taking_changed_callback) { @@ -233,6 +227,15 @@ void AppManagerImpl::OnExtensionUnloaded( OnNoteTakingExtensionChanged(); } +void AppManagerImpl::OnAvailableNoteTakingAppsUpdated() {} + +void AppManagerImpl::OnPreferredNoteTakingAppUpdated(Profile* profile) { + if (profile != primary_profile_) + return; + + OnNoteTakingExtensionChanged(); +} + void AppManagerImpl::OnNoteTakingExtensionChanged() { if (state_ == State::kInactive) return; @@ -259,9 +262,9 @@ std::string AppManagerImpl::FindLockScreenNoteTakingApp() const { chromeos::NoteTakingHelper::Get()->GetPreferredChromeAppInfo( primary_profile_); - if (!note_taking_app || + if (!note_taking_app || !note_taking_app->preferred || note_taking_app->lock_screen_support != - chromeos::NoteTakingLockScreenSupport::kSelected) { + chromeos::NoteTakingLockScreenSupport::kEnabled) { return std::string(); } diff --git a/chrome/browser/chromeos/lock_screen_apps/app_manager_impl.h b/chrome/browser/chromeos/lock_screen_apps/app_manager_impl.h index c47e48513c49..4f658cdaa315 100644 --- a/chrome/browser/chromeos/lock_screen_apps/app_manager_impl.h +++ b/chrome/browser/chromeos/lock_screen_apps/app_manager_impl.h @@ -12,7 +12,7 @@ #include "base/memory/weak_ptr.h" #include "base/scoped_observer.h" #include "chrome/browser/chromeos/lock_screen_apps/app_manager.h" -#include "components/prefs/pref_change_registrar.h" +#include "chrome/browser/chromeos/note_taking_helper.h" #include "extensions/browser/extension_registry_observer.h" class Profile; @@ -26,6 +26,7 @@ namespace lock_screen_apps { // The default implementation of lock_screen_apps::AppManager. class AppManagerImpl : public AppManager, + public chromeos::NoteTakingHelper::Observer, public extensions::ExtensionRegistryObserver { public: AppManagerImpl(); @@ -40,13 +41,17 @@ class AppManagerImpl : public AppManager, bool IsNoteTakingAppAvailable() const override; std::string GetNoteTakingAppId() const override; - // extensions::ExtensionRegistryObserver implementation: + // extensions::ExtensionRegistryObserver: void OnExtensionLoaded(content::BrowserContext* browser_context, const extensions::Extension* extension) override; void OnExtensionUnloaded(content::BrowserContext* browser_context, const extensions::Extension* extension, extensions::UnloadedExtensionReason reason) override; + // chromeos::NoteTakingHelper::Observer: + void OnAvailableNoteTakingAppsUpdated() override; + void OnPreferredNoteTakingAppUpdated(Profile* profile) override; + private: enum class State { // The manager has not yet been initialized. @@ -101,11 +106,14 @@ class AppManagerImpl : public AppManager, State state_ = State::kNotInitialized; std::string lock_screen_app_id_; - PrefChangeRegistrar pref_change_registrar_; ScopedObserver extensions_observer_; + ScopedObserver + note_taking_helper_observer_; + base::Closure note_taking_changed_callback_; // Counts app installs. Passed to app install callback as install request diff --git a/chrome/browser/chromeos/lock_screen_apps/app_manager_impl_unittest.cc b/chrome/browser/chromeos/lock_screen_apps/app_manager_impl_unittest.cc index e5d86ede9dab..eb0f234545f1 100644 --- a/chrome/browser/chromeos/lock_screen_apps/app_manager_impl_unittest.cc +++ b/chrome/browser/chromeos/lock_screen_apps/app_manager_impl_unittest.cc @@ -144,14 +144,21 @@ class LockScreenAppManagerImplTest base::Bind(&ArcSessionFactory))); chromeos::NoteTakingHelper::Initialize(); + chromeos::NoteTakingHelper::Get()->SetProfileWithEnabledLockScreenApps( + profile()); ResetAppManager(); } void TearDown() override { + // App manager has to be destroyed before NoteTakingHelper is shutdown - it + // removes itself from the NoteTakingHelper observer list during its + // destruction. + app_manager_.reset(); + + chromeos::NoteTakingHelper::Shutdown(); extensions::ExtensionSystem::Get(profile())->Shutdown(); extensions::ExtensionSystem::Get(lock_screen_profile())->Shutdown(); - chromeos::NoteTakingHelper::Shutdown(); } void InitExtensionSystem(Profile* profile) { @@ -323,8 +330,8 @@ class LockScreenAppManagerImplTest ->AddExtension(app.get()); chromeos::NoteTakingHelper::Get()->SetPreferredApp(profile, app_id); - profile->GetPrefs()->SetBoolean(prefs::kNoteTakingAppEnabledOnLockScreen, - enable_on_lock_screen); + chromeos::NoteTakingHelper::Get()->SetPreferredAppEnabledOnLockScreen( + profile, enable_on_lock_screen); return app; } @@ -515,8 +522,8 @@ TEST_P(LockScreenAppManagerImplTest, LockScreenNoteTakingDisabledWhileStarted) { lock_app->path()); EXPECT_TRUE(base::PathExists(note_taking_app->path())); - profile()->GetPrefs()->SetBoolean(prefs::kNoteTakingAppEnabledOnLockScreen, - false); + chromeos::NoteTakingHelper::Get()->SetPreferredAppEnabledOnLockScreen( + profile(), false); EXPECT_EQ(1, note_taking_changed_count()); ResetNoteTakingChangedCount(); @@ -560,8 +567,8 @@ TEST_P(LockScreenAppManagerImplTest, LockScreenNoteTakingEnabledWhileStarted) { extensions::ExtensionRegistry::EVERYTHING); EXPECT_FALSE(lock_app); - profile()->GetPrefs()->SetBoolean(prefs::kNoteTakingAppEnabledOnLockScreen, - true); + chromeos::NoteTakingHelper::Get()->SetPreferredAppEnabledOnLockScreen( + profile(), true); EXPECT_EQ(1, note_taking_changed_count()); ResetNoteTakingChangedCount(); @@ -604,7 +611,7 @@ TEST_P(LockScreenAppManagerImplTest, LockScreenNoteTakingChangedWhileStarted) { scoped_refptr dev_note_taking_app = AddTestAppWithLockScreenSupport( profile(), chromeos::NoteTakingHelper::kDevKeepExtensionId, "1.0", - false /* enable_on_lock_screen */); + true /* enable_on_lock_screen */); scoped_refptr prod_note_taking_app = AddTestAppWithLockScreenSupport( @@ -684,6 +691,81 @@ TEST_P(LockScreenAppManagerImplTest, LockScreenNoteTakingChangedWhileStarted) { EXPECT_TRUE(base::PathExists(prod_note_taking_app->path())); } +TEST_P(LockScreenAppManagerImplTest, NoteTakingChangedToLockScreenSupported) { + scoped_refptr dev_note_taking_app = + AddTestAppWithLockScreenSupport( + profile(), chromeos::NoteTakingHelper::kDevKeepExtensionId, "1.0", + true /* enable_on_lock_screen */); + + scoped_refptr prod_note_taking_app = + CreateTestAppInProfile(profile(), + chromeos::NoteTakingHelper::kProdKeepExtensionId, + "1.0", false /* supports_lock_screen */); + extensions::ExtensionSystem::Get(profile()) + ->extension_service() + ->AddExtension(prod_note_taking_app.get()); + chromeos::NoteTakingHelper::Get()->SetPreferredApp( + profile(), chromeos::NoteTakingHelper::kProdKeepExtensionId); + + // Initialize app manager - the note taking should be disabled initially + // because the preferred app (prod) is not enabled on lock screen. + InitializeAndStartAppManager(profile()); + RunExtensionServiceTaskRunner(lock_screen_profile()); + EXPECT_EQ(0, note_taking_changed_count()); + EXPECT_EQ(false, app_manager()->IsNoteTakingAppAvailable()); + + // Setting dev app, which is enabled on lock screen, as preferred will enable + // lock screen note taking, + chromeos::NoteTakingHelper::Get()->SetPreferredApp( + profile(), chromeos::NoteTakingHelper::kDevKeepExtensionId); + + EXPECT_EQ(1, note_taking_changed_count()); + ResetNoteTakingChangedCount(); + // If test app is installed asynchronously. the app won't be enabled on + // lock screen until extension service task runner tasks are run. + EXPECT_EQ(!IsInstallAsync(), app_manager()->IsNoteTakingAppAvailable()); + RunExtensionServiceTaskRunner(lock_screen_profile()); + + EXPECT_EQ(NoteTakingChangedCountOnStart(), note_taking_changed_count()); + ResetNoteTakingChangedCount(); + EXPECT_TRUE(app_manager()->IsNoteTakingAppAvailable()); + EXPECT_EQ(chromeos::NoteTakingHelper::kDevKeepExtensionId, + app_manager()->GetNoteTakingAppId()); + + // Verify the dev app copy is installed in the lock screen app profile. + const extensions::Extension* lock_app = + extensions::ExtensionRegistry::Get(lock_screen_profile()) + ->GetExtensionById(chromeos::NoteTakingHelper::kDevKeepExtensionId, + extensions::ExtensionRegistry::ENABLED); + ASSERT_TRUE(lock_app); + EXPECT_TRUE(base::PathExists(lock_app->path())); + EXPECT_EQ(GetLockScreenAppPath(dev_note_taking_app->id(), + dev_note_taking_app->VersionString()), + lock_app->path()); + + // Verify the prod app was not coppied to the lock screen profile (for + // unpacked apps, the lock screen extension path is the same as the original + // app path, so it's expected to still exist). + EXPECT_EQ( + GetParam() == TestAppLocation::kUnpacked, + base::PathExists(GetLockScreenAppPath( + prod_note_taking_app->id(), prod_note_taking_app->VersionString()))); + + app_manager()->Stop(); + + // Stopping app manager will disable lock screen note taking. + EXPECT_EQ(0, note_taking_changed_count()); + EXPECT_FALSE(app_manager()->IsNoteTakingAppAvailable()); + EXPECT_TRUE(app_manager()->GetNoteTakingAppId().empty()); + + RunExtensionServiceTaskRunner(lock_screen_profile()); + RunExtensionServiceTaskRunner(profile()); + + // Make sure original app paths are not deleted. + EXPECT_TRUE(base::PathExists(dev_note_taking_app->path())); + EXPECT_TRUE(base::PathExists(prod_note_taking_app->path())); +} + TEST_P(LockScreenAppManagerImplTest, LockScreenNoteTakingReloadedWhileStarted) { scoped_refptr note_taking_app = AddTestAppWithLockScreenSupport( diff --git a/chrome/browser/chromeos/lock_screen_apps/note_taking_browsertest.cc b/chrome/browser/chromeos/lock_screen_apps/note_taking_browsertest.cc index 08b754572950..752bf4b04d58 100644 --- a/chrome/browser/chromeos/lock_screen_apps/note_taking_browsertest.cc +++ b/chrome/browser/chromeos/lock_screen_apps/note_taking_browsertest.cc @@ -35,8 +35,9 @@ class LockScreenNoteTakingTest : public ExtensionBrowserTest { bool EnableLockScreenAppLaunch(const std::string& app_id) { chromeos::NoteTakingHelper::Get()->SetPreferredApp(profile(), app_id); - profile()->GetPrefs()->SetBoolean(prefs::kNoteTakingAppEnabledOnLockScreen, - true); + chromeos::NoteTakingHelper::Get()->SetPreferredAppEnabledOnLockScreen( + profile(), true); + session_manager::SessionManager::Get()->SetSessionState( session_manager::SessionState::LOCKED); diff --git a/chrome/browser/chromeos/lock_screen_apps/state_controller.cc b/chrome/browser/chromeos/lock_screen_apps/state_controller.cc index f0c02d11fc33..03463b033c06 100644 --- a/chrome/browser/chromeos/lock_screen_apps/state_controller.cc +++ b/chrome/browser/chromeos/lock_screen_apps/state_controller.cc @@ -15,6 +15,7 @@ #include "base/strings/string16.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/chromeos/lock_screen_apps/app_manager_impl.h" +#include "chrome/browser/chromeos/note_taking_helper.h" #include "chrome/browser/chromeos/profiles/profile_helper.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/common/chrome_paths.h" @@ -210,6 +211,9 @@ void StateController::InitializeWithCryptoKey(Profile* profile, profile, g_browser_process->local_state(), crypto_key, base_path.AppendASCII("lock_screen_app_data")); + chromeos::NoteTakingHelper::Get()->SetProfileWithEnabledLockScreenApps( + profile); + // App manager might have been set previously by a test. if (!app_manager_) app_manager_ = base::MakeUnique(); diff --git a/chrome/browser/chromeos/lock_screen_apps/state_controller_unittest.cc b/chrome/browser/chromeos/lock_screen_apps/state_controller_unittest.cc index 958884a9e95e..c9959736430a 100644 --- a/chrome/browser/chromeos/lock_screen_apps/state_controller_unittest.cc +++ b/chrome/browser/chromeos/lock_screen_apps/state_controller_unittest.cc @@ -14,8 +14,10 @@ #include "base/files/file_path.h" #include "base/memory/ptr_util.h" #include "base/test/scoped_command_line.h" +#include "chrome/browser/chromeos/arc/arc_session_manager.h" #include "chrome/browser/chromeos/lock_screen_apps/app_manager.h" #include "chrome/browser/chromeos/lock_screen_apps/state_observer.h" +#include "chrome/browser/chromeos/note_taking_helper.h" #include "chrome/browser/chromeos/profiles/profile_helper.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/test_extension_system.h" @@ -27,6 +29,8 @@ #include "chrome/test/base/testing_profile_manager.h" #include "chromeos/dbus/dbus_thread_manager.h" #include "chromeos/dbus/fake_power_manager_client.h" +#include "components/arc/arc_service_manager.h" +#include "components/arc/arc_session.h" #include "components/session_manager/core/session_manager.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_observer.h" @@ -62,6 +66,11 @@ const char kPrimaryProfileName[] = "primary_profile"; // Key for pref containing lock screen data crypto key. constexpr char kDataCryptoKeyPref[] = "lockScreenAppDataCryptoKey"; +std::unique_ptr ArcSessionFactory() { + ADD_FAILURE() << "Attempt to create arc session."; + return nullptr; +} + scoped_refptr CreateTestNoteTakingApp( const std::string& app_id) { ListBuilder action_handlers; @@ -350,6 +359,13 @@ class LockScreenAppStateTest : public BrowserWithTestWindowTest { session_manager_->SetSessionState( session_manager::SessionState::LOGIN_PRIMARY); + // Initialize arc session manager - NoteTakingHelper expects it to be set. + arc_session_manager_ = base::MakeUnique( + base::MakeUnique( + base::Bind(&ArcSessionFactory))); + + chromeos::NoteTakingHelper::Initialize(); + ASSERT_TRUE(lock_screen_apps::StateController::IsEnabled()); // Create fake lock screen app profile. @@ -381,6 +397,8 @@ class LockScreenAppStateTest : public BrowserWithTestWindowTest { state_controller_->RemoveObserver(&observer_); state_controller_->Shutdown(); + chromeos::NoteTakingHelper::Shutdown(); + session_manager_.reset(); app_manager_ = nullptr; app_window_.reset(); @@ -548,6 +566,11 @@ class LockScreenAppStateTest : public BrowserWithTestWindowTest { // owned by DBusThreadManager. chromeos::FakePowerManagerClient* power_manager_client_ = nullptr; + // The StateController does not really have dependency on ARC, but this is + // needed to properly initialize NoteTakingHelper. + std::unique_ptr arc_service_manager_; + std::unique_ptr arc_session_manager_; + std::unique_ptr session_manager_; std::unique_ptr state_controller_; diff --git a/chrome/browser/chromeos/note_taking_helper.cc b/chrome/browser/chromeos/note_taking_helper.cc index ae8144ce75dc..a177230d0455 100644 --- a/chrome/browser/chromeos/note_taking_helper.cc +++ b/chrome/browser/chromeos/note_taking_helper.cc @@ -31,6 +31,7 @@ #include "components/arc/arc_service_manager.h" #include "components/arc/intent_helper/arc_intent_helper_bridge.h" #include "components/prefs/pref_service.h" +#include "components/prefs/scoped_user_pref_update.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/notification_service.h" #include "extensions/browser/extension_registry.h" @@ -75,6 +76,58 @@ arc::mojom::IntentInfoPtr CreateIntentInfo(const GURL& clip_data_uri) { return intent; } +// Whether the app's manifest indicates that the app supports note taking on the +// lock screen. +bool IsLockScreenEnabled(const extensions::Extension* app) { + if (!lock_screen_apps::StateController::IsEnabled()) + return false; + + if (!app->permissions_data()->HasAPIPermission( + extensions::APIPermission::kLockScreen)) { + return false; + } + + return extensions::ActionHandlersInfo::HasLockScreenActionHandler( + app, app_runtime::ACTION_TYPE_NEW_NOTE); +} + +// Gets the set of apps (more specifically, their app IDs) that are allowed to +// be launched on the lock screen, if the feature is whitelisted using +// |prefs::kNoteTakingAppsLockScreenWhitelist| preference. If the pref is not +// set, this method will return null (in which case the white-list should not be +// checked). +// Note that |prefs::kNoteTakingrAppsAllowedOnLockScreen| is currently only +// expected to be set by policy (if it's set at all). +std::unique_ptr> GetAllowedLockScreenApps( + PrefService* prefs) { + const PrefService::Preference* allowed_lock_screen_apps_pref = + prefs->FindPreference(prefs::kNoteTakingAppsLockScreenWhitelist); + 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(); + + const base::ListValue* allowed_apps_list = nullptr; + if (!allowed_lock_screen_apps_value || + !allowed_lock_screen_apps_value->GetAsList(&allowed_apps_list)) { + return nullptr; + } + + auto allowed_apps = base::MakeUnique>(); + for (const base::Value& app_value : allowed_apps_list->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 const char NoteTakingHelper::kIntentAction[] = @@ -127,8 +180,7 @@ NoteTakingAppInfos NoteTakingHelper::GetAvailableApps(Profile* profile) { GetChromeApps(profile); for (const auto* app : chrome_apps) { NoteTakingLockScreenSupport lock_screen_support = - IsLockScreenEnabled(app) ? NoteTakingLockScreenSupport::kSupported - : NoteTakingLockScreenSupport::kNotSupported; + GetLockScreenSupportForChromeApp(profile, app); infos.push_back( NoteTakingAppInfo{app->name(), app->id(), false, lock_screen_support}); } @@ -143,11 +195,6 @@ NoteTakingAppInfos NoteTakingHelper::GetAvailableApps(Profile* profile) { for (auto& info : infos) { if (info.app_id == pref_app_id) { info.preferred = true; - if (info.lock_screen_support == NoteTakingLockScreenSupport::kSupported && - profile->GetPrefs()->GetBoolean( - prefs::kNoteTakingAppEnabledOnLockScreen)) { - info.lock_screen_support = NoteTakingLockScreenSupport::kSelected; - } break; } } @@ -174,23 +221,13 @@ std::unique_ptr NoteTakingHelper::GetPreferredChromeAppInfo( return nullptr; } - NoteTakingLockScreenSupport lock_screen_support = - NoteTakingLockScreenSupport::kNotSupported; - if (IsLockScreenEnabled(preferred_app)) { - if (profile->GetPrefs()->GetBoolean( - prefs::kNoteTakingAppEnabledOnLockScreen)) { - lock_screen_support = NoteTakingLockScreenSupport::kSelected; - } else { - lock_screen_support = NoteTakingLockScreenSupport::kSupported; - } - } - std::unique_ptr info = base::MakeUnique(); info->name = preferred_app->name(); info->app_id = preferred_app->id(); info->preferred = true; - info->lock_screen_support = lock_screen_support; + info->lock_screen_support = + GetLockScreenSupportForChromeApp(profile, preferred_app); return info; } @@ -199,16 +236,44 @@ void NoteTakingHelper::SetPreferredApp(Profile* profile, DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK(profile); + if (app_id == profile->GetPrefs()->GetString(prefs::kNoteTakingAppId)) + return; + + profile->GetPrefs()->SetString(prefs::kNoteTakingAppId, app_id); + + for (Observer& observer : observers_) + observer.OnPreferredNoteTakingAppUpdated(profile); +} + +bool NoteTakingHelper::SetPreferredAppEnabledOnLockScreen(Profile* profile, + bool enabled) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + DCHECK(profile); + if (profile != profile_with_enabled_lock_screen_apps_) + return false; + + std::string app_id = profile->GetPrefs()->GetString(prefs::kNoteTakingAppId); const extensions::Extension* app = extensions::ExtensionRegistry::Get(profile)->GetExtensionById( app_id, extensions::ExtensionRegistry::ENABLED); + if (!app) + return false; - if (!app || !IsLockScreenEnabled(app)) { - profile->GetPrefs()->SetBoolean(prefs::kNoteTakingAppEnabledOnLockScreen, - false); + NoteTakingLockScreenSupport current_state = + GetLockScreenSupportForChromeApp(profile, app); + + if ((enabled && current_state != NoteTakingLockScreenSupport::kSupported) || + (!enabled && current_state != NoteTakingLockScreenSupport::kEnabled)) { + return false; } - profile->GetPrefs()->SetString(prefs::kNoteTakingAppId, app_id); + profile->GetPrefs()->SetBoolean(prefs::kNoteTakingAppEnabledOnLockScreen, + enabled); + + for (Observer& observer : observers_) + observer.OnPreferredNoteTakingAppUpdated(profile); + + return true; } bool NoteTakingHelper::IsAppAvailable(Profile* profile) { @@ -259,10 +324,22 @@ void NoteTakingHelper::OnArcPlayStoreEnabledChanged(bool enabled) { android_apps_.clear(); android_apps_received_ = false; } - for (auto& observer : observers_) + for (Observer& observer : observers_) observer.OnAvailableNoteTakingAppsUpdated(); } +void NoteTakingHelper::SetProfileWithEnabledLockScreenApps(Profile* profile) { + DCHECK(!profile_with_enabled_lock_screen_apps_); + profile_with_enabled_lock_screen_apps_ = profile; + + pref_change_registrar_.Init(profile->GetPrefs()); + pref_change_registrar_.Add( + prefs::kNoteTakingAppsLockScreenWhitelist, + base::Bind(&NoteTakingHelper::OnAllowedNoteTakingAppsChanged, + base::Unretained(this))); + OnAllowedNoteTakingAppsChanged(); +} + NoteTakingHelper::NoteTakingHelper() : launch_chrome_app_callback_( base::Bind(&apps::LaunchPlatformAppWithAction)), @@ -369,19 +446,6 @@ std::vector NoteTakingHelper::GetChromeApps( return extensions; } -bool NoteTakingHelper::IsLockScreenEnabled(const extensions::Extension* app) { - if (!lock_screen_apps::StateController::IsEnabled()) - return false; - - if (!app->permissions_data()->HasAPIPermission( - extensions::APIPermission::kLockScreen)) { - return false; - } - - return extensions::ActionHandlersInfo::HasLockScreenActionHandler( - app, app_runtime::ACTION_TYPE_NEW_NOTE); -} - void NoteTakingHelper::UpdateAndroidApps() { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); auto* helper = ARC_GET_INSTANCE_FOR_METHOD( @@ -409,7 +473,7 @@ void NoteTakingHelper::OnGotAndroidApps( } android_apps_received_ = true; - for (auto& observer : observers_) + for (Observer& observer : observers_) observer.OnAvailableNoteTakingAppsUpdated(); } @@ -482,7 +546,7 @@ void NoteTakingHelper::Observe(int type, // called after an ARC-enabled user logs in: http://b/36655474 if (!play_store_enabled_ && arc::IsArcPlayStoreEnabledForProfile(profile)) { play_store_enabled_ = true; - for (auto& observer : observers_) + for (Observer& observer : observers_) observer.OnAvailableNoteTakingAppsUpdated(); } } @@ -493,7 +557,7 @@ void NoteTakingHelper::OnExtensionLoaded( if (IsWhitelistedChromeApp(extension) || extensions::ActionHandlersInfo::HasActionHandler( extension, app_runtime::ACTION_TYPE_NEW_NOTE)) { - for (auto& observer : observers_) + for (Observer& observer : observers_) observer.OnAvailableNoteTakingAppsUpdated(); } } @@ -505,7 +569,7 @@ void NoteTakingHelper::OnExtensionUnloaded( if (IsWhitelistedChromeApp(extension) || extensions::ActionHandlersInfo::HasActionHandler( extension, app_runtime::ACTION_TYPE_NEW_NOTE)) { - for (auto& observer : observers_) + for (Observer& observer : observers_) observer.OnAvailableNoteTakingAppsUpdated(); } } @@ -514,4 +578,68 @@ void NoteTakingHelper::OnShutdown(extensions::ExtensionRegistry* registry) { extension_registry_observer_.Remove(registry); } +NoteTakingLockScreenSupport NoteTakingHelper::GetLockScreenSupportForChromeApp( + Profile* profile, + const extensions::Extension* app) { + if (profile != profile_with_enabled_lock_screen_apps_) + return NoteTakingLockScreenSupport::kNotSupported; + + if (!IsLockScreenEnabled(app)) + return NoteTakingLockScreenSupport::kNotSupported; + + if (lock_screen_whitelist_state_ == AppWhitelistState::kUndetermined) + UpdateLockScreenAppsWhitelistState(); + + if (lock_screen_whitelist_state_ == AppWhitelistState::kAppsWhitelisted && + !lock_screen_apps_allowed_by_policy_.count(app->id())) { + return NoteTakingLockScreenSupport::kNotAllowedByPolicy; + } + + if (profile->GetPrefs()->GetBoolean(prefs::kNoteTakingAppEnabledOnLockScreen)) + return NoteTakingLockScreenSupport::kEnabled; + + return NoteTakingLockScreenSupport::kSupported; +} + +void NoteTakingHelper::OnAllowedNoteTakingAppsChanged() { + if (lock_screen_whitelist_state_ == AppWhitelistState::kUndetermined) + return; + + std::unique_ptr preferred_app = + GetPreferredChromeAppInfo(profile_with_enabled_lock_screen_apps_); + NoteTakingLockScreenSupport lock_screen_value_before_update = + preferred_app ? preferred_app->lock_screen_support + : NoteTakingLockScreenSupport::kNotSupported; + + UpdateLockScreenAppsWhitelistState(); + + preferred_app = + GetPreferredChromeAppInfo(profile_with_enabled_lock_screen_apps_); + NoteTakingLockScreenSupport lock_screen_value_after_update = + preferred_app ? preferred_app->lock_screen_support + : NoteTakingLockScreenSupport::kNotSupported; + + // 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) { + for (Observer& observer : observers_) { + observer.OnPreferredNoteTakingAppUpdated( + profile_with_enabled_lock_screen_apps_); + } + } +} + +void NoteTakingHelper::UpdateLockScreenAppsWhitelistState() { + std::unique_ptr> whitelist = GetAllowedLockScreenApps( + profile_with_enabled_lock_screen_apps_->GetPrefs()); + + if (whitelist) { + lock_screen_whitelist_state_ = AppWhitelistState::kAppsWhitelisted; + lock_screen_apps_allowed_by_policy_.swap(*whitelist); + } else { + lock_screen_whitelist_state_ = AppWhitelistState::kNoAppWhitelist; + lock_screen_apps_allowed_by_policy_.clear(); + } +} + } // namespace chromeos diff --git a/chrome/browser/chromeos/note_taking_helper.h b/chrome/browser/chromeos/note_taking_helper.h index 691b7efd3bb9..958be717c822 100644 --- a/chrome/browser/chromeos/note_taking_helper.h +++ b/chrome/browser/chromeos/note_taking_helper.h @@ -6,6 +6,7 @@ #define CHROME_BROWSER_CHROMEOS_NOTE_TAKING_HELPER_H_ #include +#include #include #include @@ -16,6 +17,7 @@ #include "chrome/browser/chromeos/arc/arc_session_manager.h" #include "components/arc/common/intent_helper.mojom.h" #include "components/arc/intent_helper/arc_intent_helper_observer.h" +#include "components/prefs/pref_change_registrar.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" @@ -34,6 +36,7 @@ class BrowserContext; } // namespace content namespace extensions { +class Extension; class ExtensionRegistry; namespace api { namespace app_runtime { @@ -45,17 +48,23 @@ struct ActionData; namespace chromeos { // Describes an app's level of support for lock screen enabled note taking. +// IMPORTANT: These constants are used in settings UI, so be careful about +// reordering/adding/removing items. enum class NoteTakingLockScreenSupport { // The app does not support note taking on lock screen. - kNotSupported, - // The app supports note taking on lock screen, but is not selected as the + kNotSupported = 0, + // The app supports lock screen note taking, but is not allowed to run on the + // lock screen due to policy settings. + kNotAllowedByPolicy = 1, + // The app supports note taking on lock screen, but is not enabled as a // lock screen note taking app by the user. This state implies that the user - // can be offered to select this app as the lock screen note taking handler. - kSupported, - // The app is selected as the lock screen note taking app by the user. - // Currently, only the preferred note taking app can be selected as the lock - // screen handler. - kSelected + // can be offered to enable this app as the lock screen note taking handler. + kSupported = 2, + // The app is enabled by the user to run as a note taking handler on the lock + // screen. Note that, while more than one app can be in enabled state at a + // same time, currently only the preferred note taking app will be launchable + // from the lock screen UI. + kEnabled = 3, }; // Information about an installed note-taking app. @@ -92,6 +101,10 @@ class NoteTakingHelper : public arc::ArcIntentHelperObserver, // Called when the list of available apps that will be returned by // GetAvailableApps() changes or when |play_store_enabled_| changes state. virtual void OnAvailableNoteTakingAppsUpdated() = 0; + + // Called when the preferred note taking app (or its properties) in + // |profile| is updated. + virtual void OnPreferredNoteTakingAppUpdated(Profile* profile) = 0; }; // Describes the result of an attempt to launch a note-taking app. Values must @@ -167,6 +180,11 @@ class NoteTakingHelper : public arc::ArcIntentHelperObserver, // NoteTakingAppInfo object. void SetPreferredApp(Profile* profile, const std::string& app_id); + // Enables or disables preferred note taking apps from running on the lock + // screen. + // Returns whether the app status changed. + bool SetPreferredAppEnabledOnLockScreen(Profile* profile, bool enabled); + // Returns true if an app that can be used to take notes is available. UI // surfaces that call LaunchAppForNewNote() should be hidden otherwise. bool IsAppAvailable(Profile* profile); @@ -182,7 +200,21 @@ class NoteTakingHelper : public arc::ArcIntentHelperObserver, // arc::ArcSessionManager::Observer: void OnArcPlayStoreEnabledChanged(bool enabled) override; + // Sets the profile which supports note taking apps on the lock screen. + void SetProfileWithEnabledLockScreenApps(Profile* profile); + private: + // The state of app ID whitelist cache (used for determining the state of + // note-taking apps whtielisted for the lock screen). + enum class AppWhitelistState { + // The whitelist value has not yet been determined. + kUndetermined, + // The app ID whitelist does not exist in the profile. + kNoAppWhitelist, + // The app ID whitelist exists in the profile. + kAppsWhitelisted + }; + NoteTakingHelper(); ~NoteTakingHelper() override; @@ -195,10 +227,6 @@ class NoteTakingHelper : public arc::ArcIntentHelperObserver, std::vector GetChromeApps( Profile* profile) const; - // Returns whether |app| is a note taking app that supports note taking on - // lock screen. - bool IsLockScreenEnabled(const extensions::Extension* app); - // Requests a list of Android note-taking apps from ARC. void UpdateAndroidApps(); @@ -225,6 +253,22 @@ class NoteTakingHelper : public arc::ArcIntentHelperObserver, extensions::UnloadedExtensionReason reason) override; void OnShutdown(extensions::ExtensionRegistry* registry) override; + // Determines the state of the |app|'s support for lock screen note taking. + // |profile| - The profile in which the app is installed. + NoteTakingLockScreenSupport GetLockScreenSupportForChromeApp( + Profile* profile, + const extensions::Extension* app); + + // Called when kNoteTakingAppsLockScreenWhitelist pref changes for + // |profile_with_enabled_lock_screen_apps_|. + void OnAllowedNoteTakingAppsChanged(); + + // Updates the cached whitelist of note-taking apps allowed on the lock + // screen - it sets |lock_screen_whitelist_state_| and + // |lock_screen_apps_allowed_by_policy_| to values appropriate for the current + // |profile_with_enabled_lock_screen_apps_| state. + void UpdateLockScreenAppsWhitelistState(); + // True iff Play Store is enabled (i.e. per the checkbox on the settings // page). Note that ARC may not be fully started yet when this is true, but it // is expected to start eventually. Similarly, ARC may not be fully shut down @@ -250,6 +294,26 @@ class NoteTakingHelper : public arc::ArcIntentHelperObserver, extensions::ExtensionRegistryObserver> extension_registry_observer_; + // The profile for which lock screen apps are enabled, + Profile* profile_with_enabled_lock_screen_apps_ = nullptr; + + // The current AppWhitelistState for lock screen note taking in + // |profile_with_enabled_lock_screen_apps_|. If kAppsWhitelisted, + // |lock_screen_apps_allowed_by_policy_| should contain the set of whitelisted + // app IDs. + AppWhitelistState lock_screen_whitelist_state_ = + AppWhitelistState::kUndetermined; + + // If |lock_screen_whitelist_state_| is kAppsWhitelisted, contains all app + // IDs that are allowed to handle new-note action on the lock screen. The set + // should only be used for apps from |profile_with_enabled_lock_screen_apps_| + // and when |lock_screen_whitelist_state_| equals kAppsWhitelisted. + std::set lock_screen_apps_allowed_by_policy_; + + // Tracks kNoteTakingAppsLockScreenWhitelist pref for the profile for which + // lock screen apps are enabled. + PrefChangeRegistrar pref_change_registrar_; + base::ObserverList observers_; content::NotificationRegistrar registrar_; diff --git a/chrome/browser/chromeos/note_taking_helper_unittest.cc b/chrome/browser/chromeos/note_taking_helper_unittest.cc index c6ef5a2a9fac..a46023e884c4 100644 --- a/chrome/browser/chromeos/note_taking_helper_unittest.cc +++ b/chrome/browser/chromeos/note_taking_helper_unittest.cc @@ -13,6 +13,7 @@ #include "base/macros.h" #include "base/memory/ptr_util.h" #include "base/run_loop.h" +#include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/test/histogram_tester.h" @@ -71,8 +72,8 @@ std::string GetAppString(const std::string& id, const std::string& name, bool preferred, NoteTakingLockScreenSupport lock_screen_support) { - return base::StringPrintf("%s %s %d %d", id.c_str(), name.c_str(), preferred, - static_cast(lock_screen_support)); + return base::StringPrintf("{%s, %s, %d, %d}", id.c_str(), name.c_str(), + preferred, static_cast(lock_screen_support)); } std::string GetAppString(const NoteTakingAppInfo& info) { return GetAppString(info.app_id, info.name, info.preferred, @@ -119,13 +120,25 @@ class TestObserver : public NoteTakingHelper::Observer { int num_updates() const { return num_updates_; } void reset_num_updates() { num_updates_ = 0; } + const std::vector preferred_app_updates() const { + return preferred_app_updates_; + } + void clear_preferred_app_updates() { preferred_app_updates_.clear(); } + private: // NoteTakingHelper::Observer: void OnAvailableNoteTakingAppsUpdated() override { num_updates_++; } + void OnPreferredNoteTakingAppUpdated(Profile* profile) override { + preferred_app_updates_.push_back(profile); + } + // Number of times that OnAvailableNoteTakingAppsUpdated() has been called. int num_updates_ = 0; + // Profiles for which OnPreferredNoteTakingAppUpdated was called. + std::vector preferred_app_updates_; + DISALLOW_COPY_AND_ASSIGN(TestObserver); }; @@ -211,6 +224,7 @@ class NoteTakingHelperTest : public BrowserWithTestWindowTest, profile()->GetPrefs()->SetBoolean(prefs::kArcEnabled, flags & ENABLE_PLAY_STORE); NoteTakingHelper::Initialize(); + NoteTakingHelper::Get()->SetProfileWithEnabledLockScreenApps(profile()); NoteTakingHelper::Get()->set_launch_chrome_app_callback_for_test(base::Bind( &NoteTakingHelperTest::LaunchChromeApp, base::Unretained(this))); } @@ -275,28 +289,132 @@ class NoteTakingHelperTest : public BrowserWithTestWindowTest, } void UninstallExtension(const extensions::Extension* extension, Profile* profile) { + base::string16 error; extensions::ExtensionSystem::Get(profile) ->extension_service() - ->UnloadExtension(extension->id(), - extensions::UnloadedExtensionReason::UNINSTALL); + ->UninstallExtension( + extension->id(), + extensions::UninstallReason::UNINSTALL_REASON_FOR_TESTING, + base::Closure(), &error); + } + + scoped_refptr CreateAndInstallLockScreenApp( + const std::string& id, + const std::string& app_name, + Profile* profile) { + return CreateAndInstallLockScreenAppWithPermissions( + id, app_name, extensions::ListBuilder().Append("lockScreen").Build(), + profile); + } + + scoped_refptr + CreateAndInstallLockScreenAppWithPermissions( + const std::string& id, + const std::string& app_name, + std::unique_ptr permissions, + Profile* profile) { + std::unique_ptr lock_enabled_action_handler = + extensions::ListBuilder() + .Append(extensions::DictionaryBuilder() + .Set("action", app_runtime::ToString( + app_runtime::ACTION_TYPE_NEW_NOTE)) + .SetBoolean("enabled_on_lock_screen", true) + .Build()) + .Build(); + + scoped_refptr keep_extension = + CreateExtension(id, app_name, std::move(permissions), + std::move(lock_enabled_action_handler)); + InstallExtension(keep_extension.get(), profile); + + return keep_extension; } // BrowserWithTestWindowTest: TestingProfile* CreateProfile() override { // Ensure that the profile created by BrowserWithTestWindowTest is // registered with |profile_manager_|. - return profile_manager_->CreateTestingProfile(kTestProfileName); + auto prefs = + base::MakeUnique(); + chrome::RegisterUserProfilePrefs(prefs->registry()); + profile_prefs_ = prefs.get(); + return profile_manager_->CreateTestingProfile( + kTestProfileName, std::move(prefs), base::ASCIIToUTF16("Test profile"), + 1 /*avatar_id*/, std::string() /*supervised_user_id*/, + TestingProfile::TestingFactories()); } + void DestroyProfile(TestingProfile* profile) override { + profile_prefs_ = nullptr; return profile_manager_->DeleteTestingProfile(kTestProfileName); } + testing::AssertionResult PreferredAppMatches(Profile* profile, + NoteTakingAppInfo app_info) { + std::unique_ptr preferred_app = + helper()->GetPreferredChromeAppInfo(profile); + if (!preferred_app) + return ::testing::AssertionFailure() << "No preferred app"; + + std::string expected = GetAppString(app_info); + std::string actual = GetAppString(*preferred_app); + if (expected != actual) { + return ::testing::AssertionFailure() << "Expected: " << expected << " " + << "Actual: " << actual; + } + return ::testing::AssertionSuccess(); + } + + std::string NoteAppInfoListToString( + const std::vector& apps) { + std::vector app_strings; + for (const auto& app : apps) + app_strings.push_back(GetAppString(app)); + return base::JoinString(app_strings, ","); + } + + testing::AssertionResult AvailableAppsMatch( + Profile* profile, + const std::vector& expected_apps) { + std::vector actual_apps = + helper()->GetAvailableApps(profile); + if (actual_apps.size() != expected_apps.size()) { + return ::testing::AssertionFailure() + << "Size mismatch. " + << "Expected: [" << NoteAppInfoListToString(expected_apps) << "] " + << "Actual: [" << NoteAppInfoListToString(actual_apps) << "]"; + } + + std::unique_ptr<::testing::AssertionResult> failure; + for (size_t i = 0; i < expected_apps.size(); ++i) { + std::string expected = GetAppString(expected_apps[i]); + std::string actual = GetAppString(actual_apps[i]); + if (expected != actual) { + if (!failure) { + failure = base::MakeUnique<::testing::AssertionResult>( + ::testing::AssertionFailure()); + } + *failure << "Error at index " << i << ": " + << "Expected: " << expected << " " + << "Actual: " << actual; + } + } + + if (failure) + return *failure; + return ::testing::AssertionSuccess(); + } + // Info about launched Chrome apps, in the order they were launched. std::vector launched_chrome_apps_; arc::FakeIntentHelperInstance intent_helper_; std::unique_ptr profile_manager_; + // Pointer to the primary profile (returned by |profile()|) prefs - owned by + // the profile. + sync_preferences::TestingPrefServiceSyncable* profile_prefs_ = nullptr; + private: // Callback registered with the helper to record Chrome app launch requests. void LaunchChromeApp(content::BrowserContext* passed_context, @@ -341,28 +459,22 @@ TEST_P(NoteTakingHelperTest, ListChromeApps) { CreateExtension(NoteTakingHelper::kProdKeepExtensionId, kProdKeepAppName); InstallExtension(prod_extension.get(), profile()); EXPECT_TRUE(helper()->IsAppAvailable(profile())); - std::vector apps = helper()->GetAvailableApps(profile()); - ASSERT_EQ(1u, apps.size()); - EXPECT_EQ(GetAppString(NoteTakingHelper::kProdKeepExtensionId, - kProdKeepAppName, false /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(apps[0])); + EXPECT_TRUE(AvailableAppsMatch( + profile(), + {{kProdKeepAppName, NoteTakingHelper::kProdKeepExtensionId, + false /*preferred*/, NoteTakingLockScreenSupport::kNotSupported}})); // If the dev version is also installed, it should be listed before the prod // version. scoped_refptr dev_extension = CreateExtension(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName); InstallExtension(dev_extension.get(), profile()); - apps = helper()->GetAvailableApps(profile()); - ASSERT_EQ(2u, apps.size()); - EXPECT_EQ(GetAppString(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName, - false /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(apps[0])); - EXPECT_EQ(GetAppString(NoteTakingHelper::kProdKeepExtensionId, - kProdKeepAppName, false /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(apps[1])); + EXPECT_TRUE(AvailableAppsMatch( + profile(), + {{kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + false /*preferred*/, NoteTakingLockScreenSupport::kNotSupported}, + {kProdKeepAppName, NoteTakingHelper::kProdKeepExtensionId, + false /*preferred*/, NoteTakingLockScreenSupport::kNotSupported}})); EXPECT_FALSE(helper()->GetPreferredChromeAppInfo(profile())); // Now install a random extension and check that it's ignored. @@ -371,38 +483,27 @@ TEST_P(NoteTakingHelperTest, ListChromeApps) { scoped_refptr other_extension = CreateExtension(kOtherId, kOtherName); InstallExtension(other_extension.get(), profile()); - apps = helper()->GetAvailableApps(profile()); - ASSERT_EQ(2u, apps.size()); - EXPECT_EQ(GetAppString(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName, - false /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(apps[0])); - EXPECT_EQ(GetAppString(NoteTakingHelper::kProdKeepExtensionId, - kProdKeepAppName, false /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(apps[1])); + EXPECT_TRUE(AvailableAppsMatch( + profile(), + {{kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + false /*preferred*/, NoteTakingLockScreenSupport::kNotSupported}, + {kProdKeepAppName, NoteTakingHelper::kProdKeepExtensionId, + false /*preferred*/, NoteTakingLockScreenSupport::kNotSupported}})); EXPECT_FALSE(helper()->GetPreferredChromeAppInfo(profile())); // Mark the prod version as preferred. helper()->SetPreferredApp(profile(), NoteTakingHelper::kProdKeepExtensionId); - apps = helper()->GetAvailableApps(profile()); - ASSERT_EQ(2u, apps.size()); - EXPECT_EQ(GetAppString(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName, - false /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(apps[0])); - EXPECT_EQ(GetAppString(NoteTakingHelper::kProdKeepExtensionId, - kProdKeepAppName, true /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(apps[1])); - - std::unique_ptr preferred_info = - helper()->GetPreferredChromeAppInfo(profile()); - ASSERT_TRUE(preferred_info); - EXPECT_EQ(GetAppString(NoteTakingHelper::kProdKeepExtensionId, - kProdKeepAppName, true /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(*preferred_info)); + EXPECT_TRUE(AvailableAppsMatch( + profile(), + {{kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + false /*preferred*/, NoteTakingLockScreenSupport::kNotSupported}, + {kProdKeepAppName, NoteTakingHelper::kProdKeepExtensionId, + true /*preferred*/, NoteTakingLockScreenSupport::kNotSupported}})); + + EXPECT_TRUE(PreferredAppMatches( + profile(), + {kProdKeepAppName, NoteTakingHelper::kProdKeepExtensionId, + true /*preferred*/, NoteTakingLockScreenSupport::kNotSupported})); } TEST_P(NoteTakingHelperTest, ListChromeAppsWithLockScreenNotesSupported) { @@ -423,41 +524,24 @@ TEST_P(NoteTakingHelperTest, ListChromeAppsWithLockScreenNotesSupported) { nullptr /* permissions */, std::move(lock_disabled_action_handler)); InstallExtension(prod_extension.get(), profile()); EXPECT_TRUE(helper()->IsAppAvailable(profile())); - std::vector apps = helper()->GetAvailableApps(profile()); - ASSERT_EQ(1u, apps.size()); - EXPECT_EQ(GetAppString(NoteTakingHelper::kProdKeepExtensionId, - kProdKeepAppName, false /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(apps[0])); + EXPECT_TRUE(AvailableAppsMatch( + profile(), + {{kProdKeepAppName, NoteTakingHelper::kProdKeepExtensionId, + false /*preferred*/, NoteTakingLockScreenSupport::kNotSupported}})); EXPECT_FALSE(helper()->GetPreferredChromeAppInfo(profile())); - std::unique_ptr lock_enabled_action_handler = - extensions::ListBuilder() - .Append(extensions::DictionaryBuilder() - .Set("action", app_runtime::ToString( - app_runtime::ACTION_TYPE_NEW_NOTE)) - .SetBoolean("enabled_on_lock_screen", true) - .Build()) - .Build(); - // Install additional Keep app - one that supports lock screen note taking. // This app should be reported to support note taking (given that // enable-lock-screen-apps flag is set). scoped_refptr dev_extension = - CreateExtension(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName, - extensions::ListBuilder().Append("lockScreen").Build(), - std::move(lock_enabled_action_handler)); - InstallExtension(dev_extension.get(), profile()); - apps = helper()->GetAvailableApps(profile()); - ASSERT_EQ(2u, apps.size()); - EXPECT_EQ(GetAppString(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName, - false /* preferred */, - NoteTakingLockScreenSupport::kSupported), - GetAppString(apps[0])); - EXPECT_EQ(GetAppString(NoteTakingHelper::kProdKeepExtensionId, - kProdKeepAppName, false /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(apps[1])); + CreateAndInstallLockScreenApp(NoteTakingHelper::kDevKeepExtensionId, + kDevKeepAppName, profile()); + EXPECT_TRUE(AvailableAppsMatch( + profile(), + {{kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + false /*preferred*/, NoteTakingLockScreenSupport::kSupported}, + {kProdKeepAppName, NoteTakingHelper::kProdKeepExtensionId, + false /*preferred*/, NoteTakingLockScreenSupport::kNotSupported}})); EXPECT_FALSE(helper()->GetPreferredChromeAppInfo(profile())); } @@ -467,67 +551,42 @@ TEST_P(NoteTakingHelperTest, PreferredAppEnabledOnLockScreen) { ASSERT_FALSE(helper()->IsAppAvailable(profile())); ASSERT_TRUE(helper()->GetAvailableApps(profile()).empty()); - std::unique_ptr lock_enabled_action_handler = - extensions::ListBuilder() - .Append(extensions::DictionaryBuilder() - .Set("action", app_runtime::ToString( - app_runtime::ACTION_TYPE_NEW_NOTE)) - .SetBoolean("enabled_on_lock_screen", true) - .Build()) - .Build(); - // Install lock screen enabled Keep note taking app. scoped_refptr dev_extension = - CreateExtension(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName, - extensions::ListBuilder().Append("lockScreen").Build(), - std::move(lock_enabled_action_handler)); - InstallExtension(dev_extension.get(), profile()); + CreateAndInstallLockScreenApp(NoteTakingHelper::kDevKeepExtensionId, + kDevKeepAppName, profile()); // Verify that the app is reported to support lock screen note taking. - std::vector apps = helper()->GetAvailableApps(profile()); - ASSERT_EQ(1u, apps.size()); - EXPECT_EQ(GetAppString(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName, - false /* preferred */, - NoteTakingLockScreenSupport::kSupported), - GetAppString(apps[0])); + EXPECT_TRUE(AvailableAppsMatch( + profile(), + {{kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + false /*preferred*/, NoteTakingLockScreenSupport::kSupported}})); EXPECT_FALSE(helper()->GetPreferredChromeAppInfo(profile())); // When the lock screen note taking pref is set and the Keep app is set as the // preferred note taking app, the app should be reported as selected as lock // screen note taking app. helper()->SetPreferredApp(profile(), NoteTakingHelper::kDevKeepExtensionId); - profile()->GetPrefs()->SetBoolean(prefs::kNoteTakingAppEnabledOnLockScreen, - true); - apps = helper()->GetAvailableApps(profile()); - ASSERT_EQ(1u, apps.size()); - EXPECT_EQ(GetAppString(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName, - true /* preferred */, - NoteTakingLockScreenSupport::kSelected), - GetAppString(apps[0])); - std::unique_ptr preferred_info = - helper()->GetPreferredChromeAppInfo(profile()); - ASSERT_TRUE(preferred_info); - EXPECT_EQ(GetAppString(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName, - true /* preferred */, - NoteTakingLockScreenSupport::kSelected), - GetAppString(*preferred_info)); - - // When lock screen note taking pref is reset, the app should not be reported - // as selected on lock screen. - profile()->GetPrefs()->SetBoolean(prefs::kNoteTakingAppEnabledOnLockScreen, - false); - apps = helper()->GetAvailableApps(profile()); - ASSERT_EQ(1u, apps.size()); - EXPECT_EQ(GetAppString(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName, - true /* preferred */, - NoteTakingLockScreenSupport::kSupported), - GetAppString(apps[0])); - preferred_info = helper()->GetPreferredChromeAppInfo(profile()); - ASSERT_TRUE(preferred_info); - EXPECT_EQ(GetAppString(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName, - true /* preferred */, - NoteTakingLockScreenSupport::kSupported), - GetAppString(*preferred_info)); + helper()->SetPreferredAppEnabledOnLockScreen(profile(), true); + + EXPECT_TRUE(AvailableAppsMatch( + profile(), + {{kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + true /*preferred*/, NoteTakingLockScreenSupport::kEnabled}})); + EXPECT_TRUE(PreferredAppMatches( + profile(), {kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + true /*preferred*/, NoteTakingLockScreenSupport::kEnabled})); + + helper()->SetPreferredAppEnabledOnLockScreen(profile(), false); + + EXPECT_TRUE(AvailableAppsMatch( + profile(), + {{kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + true /*preferred*/, NoteTakingLockScreenSupport::kSupported}})); + EXPECT_TRUE(PreferredAppMatches( + profile(), + {kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + true /*preferred*/, NoteTakingLockScreenSupport::kSupported})); } TEST_P(NoteTakingHelperTest, PreferredAppWithNoLockScreenPermission) { @@ -536,29 +595,22 @@ TEST_P(NoteTakingHelperTest, PreferredAppWithNoLockScreenPermission) { ASSERT_FALSE(helper()->IsAppAvailable(profile())); ASSERT_TRUE(helper()->GetAvailableApps(profile()).empty()); - std::unique_ptr lock_enabled_action_handler = - extensions::ListBuilder() - .Append(extensions::DictionaryBuilder() - .Set("action", app_runtime::ToString( - app_runtime::ACTION_TYPE_NEW_NOTE)) - .SetBoolean("enabled_on_lock_screen", true) - .Build()) - .Build(); - // Install lock screen enabled Keep note taking app, but wihtout lock screen // permission listed. - scoped_refptr dev_extension = CreateExtension( - NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName, - nullptr /* permissions */, std::move(lock_enabled_action_handler)); - InstallExtension(dev_extension.get(), profile()); + scoped_refptr dev_extension = + CreateAndInstallLockScreenAppWithPermissions( + NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName, nullptr, + profile()); + helper()->SetPreferredApp(profile(), NoteTakingHelper::kDevKeepExtensionId); - // Verify that the app is not reported to support lock screen note taking. - std::vector apps = helper()->GetAvailableApps(profile()); - ASSERT_EQ(1u, apps.size()); - EXPECT_EQ(GetAppString(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName, - false /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(apps[0])); + EXPECT_TRUE(AvailableAppsMatch( + profile(), + {{kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + true /*preferred*/, NoteTakingLockScreenSupport::kNotSupported}})); + EXPECT_TRUE(PreferredAppMatches( + profile(), + {kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + true /*preferred*/, NoteTakingLockScreenSupport::kNotSupported})); } TEST_P(NoteTakingHelperTest, @@ -568,90 +620,50 @@ TEST_P(NoteTakingHelperTest, ASSERT_FALSE(helper()->IsAppAvailable(profile())); ASSERT_TRUE(helper()->GetAvailableApps(profile()).empty()); - std::unique_ptr lock_enabled_action_handler = - extensions::ListBuilder() - .Append(extensions::DictionaryBuilder() - .Set("action", app_runtime::ToString( - app_runtime::ACTION_TYPE_NEW_NOTE)) - .SetBoolean("enabled_on_lock_screen", true) - .Build()) - .Build(); - // Install dev Keep app that supports lock screen note taking. scoped_refptr dev_extension = - CreateExtension(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName, - extensions::ListBuilder().Append("lockScreen").Build(), - lock_enabled_action_handler->CreateDeepCopy()); - InstallExtension(dev_extension.get(), profile()); + CreateAndInstallLockScreenApp(NoteTakingHelper::kDevKeepExtensionId, + kDevKeepAppName, profile()); // Install third-party app that doesn't support lock screen note taking. const extensions::ExtensionId kNewNoteId = crx_file::id_util::GenerateId("a"); const std::string kName = "Some App"; scoped_refptr has_new_note = - CreateExtension(kNewNoteId, kName, extensions::ListBuilder().Build(), - lock_enabled_action_handler->CreateDeepCopy()); - InstallExtension(has_new_note.get(), profile()); + CreateAndInstallLockScreenAppWithPermissions(kNewNoteId, kName, nullptr, + profile()); // Verify that only Keep app is reported to support lock screen note taking. - std::vector apps = helper()->GetAvailableApps(profile()); - ASSERT_EQ(2u, apps.size()); - EXPECT_EQ(GetAppString(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName, - false /* preferred */, - NoteTakingLockScreenSupport::kSupported), - GetAppString(apps[0])); - EXPECT_EQ(GetAppString(kNewNoteId, kName, false /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(apps[1])); + EXPECT_TRUE(AvailableAppsMatch( + profile(), + {{kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + false /*preferred*/, NoteTakingLockScreenSupport::kSupported}, + {kName, kNewNoteId, false /*preferred*/, + NoteTakingLockScreenSupport::kNotSupported}})); // When the Keep app is set as preferred app, and note taking on lock screen // is enabled, the keep app should be reported to be selected as the lock // screen note taking app. helper()->SetPreferredApp(profile(), NoteTakingHelper::kDevKeepExtensionId); - profile()->GetPrefs()->SetBoolean(prefs::kNoteTakingAppEnabledOnLockScreen, - true); - apps = helper()->GetAvailableApps(profile()); - ASSERT_EQ(2u, apps.size()); - EXPECT_EQ(GetAppString(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName, - true /* preferred */, - NoteTakingLockScreenSupport::kSelected), - GetAppString(apps[0])); - EXPECT_EQ(GetAppString(kNewNoteId, kName, false /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(apps[1])); + helper()->SetPreferredAppEnabledOnLockScreen(profile(), true); + + EXPECT_TRUE(AvailableAppsMatch( + profile(), {{kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + true /*preferred*/, NoteTakingLockScreenSupport::kEnabled}, + {kName, kNewNoteId, false /*preferred*/, + NoteTakingLockScreenSupport::kNotSupported}})); // When a third party app (which does not support lock screen note taking) is - // set as the preferred app, Keep app should stop being reported as the - // selected note taking app. + // set as the preferred app, Keep app's lock screen support state remain + // enabled - even though it will not be launchable from the lock screen. helper()->SetPreferredApp(profile(), kNewNoteId); - apps = helper()->GetAvailableApps(profile()); - ASSERT_EQ(2u, apps.size()); - EXPECT_EQ(GetAppString(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName, - false /* preferred */, - NoteTakingLockScreenSupport::kSupported), - GetAppString(apps[0])); - EXPECT_EQ(GetAppString(kNewNoteId, kName, true /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(apps[1])); - - // Setting an app that does not support lock screen note taking should reset - // the 'enable lock screen note taking' preference to |false|. - EXPECT_FALSE(profile()->GetPrefs()->GetBoolean( - prefs::kNoteTakingAppEnabledOnLockScreen)); - - // Setting the Keep app as the preferred app again should not re-anable lock - // screen note taking. - helper()->SetPreferredApp(profile(), NoteTakingHelper::kDevKeepExtensionId); - apps = helper()->GetAvailableApps(profile()); - ASSERT_EQ(2u, apps.size()); - EXPECT_EQ(GetAppString(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName, - true /* preferred */, - NoteTakingLockScreenSupport::kSupported), - GetAppString(apps[0])); - EXPECT_EQ(GetAppString(kNewNoteId, kName, false /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(apps[1])); - EXPECT_FALSE(profile()->GetPrefs()->GetBoolean( - prefs::kNoteTakingAppEnabledOnLockScreen)); + EXPECT_TRUE(AvailableAppsMatch( + profile(), {{kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + false /*preferred*/, NoteTakingLockScreenSupport::kEnabled}, + {kName, kNewNoteId, true /*preferred*/, + NoteTakingLockScreenSupport::kNotSupported}})); + EXPECT_TRUE(PreferredAppMatches( + profile(), {kName, kNewNoteId, true /*preferred*/, + NoteTakingLockScreenSupport::kNotSupported})); } TEST_P(NoteTakingHelperTest, @@ -661,31 +673,17 @@ TEST_P(NoteTakingHelperTest, ASSERT_FALSE(helper()->IsAppAvailable(profile())); ASSERT_TRUE(helper()->GetAvailableApps(profile()).empty()); - std::unique_ptr lock_enabled_action_handler = - extensions::ListBuilder() - .Append(extensions::DictionaryBuilder() - .Set("action", app_runtime::ToString( - app_runtime::ACTION_TYPE_NEW_NOTE)) - .SetBoolean("enabled_on_lock_screen", true) - .Build()) - .Build(); - scoped_refptr dev_extension = - CreateExtension(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName, - extensions::ListBuilder().Append("lockScreen").Build(), - std::move(lock_enabled_action_handler)); - InstallExtension(dev_extension.get(), profile()); + CreateAndInstallLockScreenApp(NoteTakingHelper::kDevKeepExtensionId, + kDevKeepAppName, profile()); helper()->SetPreferredApp(profile(), NoteTakingHelper::kDevKeepExtensionId); - profile()->GetPrefs()->SetBoolean(prefs::kNoteTakingAppEnabledOnLockScreen, - true); - - std::vector apps = helper()->GetAvailableApps(profile()); - ASSERT_EQ(1u, apps.size()); - EXPECT_EQ(GetAppString(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName, - true /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(apps[0])); + helper()->SetPreferredAppEnabledOnLockScreen(profile(), true); + + EXPECT_TRUE(AvailableAppsMatch( + profile(), + {{kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + true /*preferred*/, NoteTakingLockScreenSupport::kNotSupported}})); } // Verify that lock screen apps are not supported if the feature is not enabled. @@ -695,26 +693,14 @@ TEST_P(NoteTakingHelperTest, LockScreenAppsSupportNotEnabled) { ASSERT_FALSE(helper()->IsAppAvailable(profile())); ASSERT_TRUE(helper()->GetAvailableApps(profile()).empty()); - std::unique_ptr lock_enabled_action_handler = - extensions::ListBuilder() - .Append(extensions::DictionaryBuilder() - .Set("action", app_runtime::ToString( - app_runtime::ACTION_TYPE_NEW_NOTE)) - .SetBoolean("enabled_on_lock_screen", true) - .Build()) - .Build(); - scoped_refptr dev_extension = - CreateExtension(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName, - extensions::ListBuilder().Append("lockScreen").Build(), - std::move(lock_enabled_action_handler)); - InstallExtension(dev_extension.get(), profile()); - std::vector apps = helper()->GetAvailableApps(profile()); - ASSERT_EQ(1u, apps.size()); - EXPECT_EQ(GetAppString(NoteTakingHelper::kDevKeepExtensionId, kDevKeepAppName, - false /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(apps[0])); + CreateAndInstallLockScreenApp(NoteTakingHelper::kDevKeepExtensionId, + kDevKeepAppName, profile()); + + EXPECT_TRUE(AvailableAppsMatch( + profile(), + {{kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + false /*preferred*/, NoteTakingLockScreenSupport::kNotSupported}})); } // Verify the note helper detects apps with "new_note" "action_handler" manifest @@ -746,11 +732,9 @@ TEST_P(NoteTakingHelperTest, CustomChromeApps) { InstallExtension(none.get(), profile()); // Only the "new_note" extension is returned from GetAvailableApps. - std::vector apps = helper()->GetAvailableApps(profile()); - ASSERT_EQ(1u, apps.size()); - EXPECT_EQ(GetAppString(kNewNoteId, kName, false /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(apps[0])); + EXPECT_TRUE(AvailableAppsMatch( + profile(), {{kName, kNewNoteId, false /*preferred*/, + NoteTakingLockScreenSupport::kNotSupported}})); } // Verify that non-whitelisted apps cannot be enabled on lock screen. @@ -758,30 +742,14 @@ TEST_P(NoteTakingHelperTest, CustomLockScreenEnabledApps) { Init(ENABLE_PALETTE & ENABLE_LOCK_SCREEN_APPS); const extensions::ExtensionId kNewNoteId = crx_file::id_util::GenerateId("a"); - const extensions::ExtensionId kEmptyArrayId = - crx_file::id_util::GenerateId("b"); - const extensions::ExtensionId kEmptyId = crx_file::id_util::GenerateId("c"); const std::string kName = "Some App"; - std::unique_ptr lock_enabled_action_handler = - extensions::ListBuilder() - .Append(extensions::DictionaryBuilder() - .Set("action", app_runtime::ToString( - app_runtime::ACTION_TYPE_NEW_NOTE)) - .SetBoolean("enabled_on_lock_screen", true) - .Build()) - .Build(); - scoped_refptr has_new_note = CreateExtension( - kNewNoteId, kName, extensions::ListBuilder().Append("lockScreen").Build(), - std::move(lock_enabled_action_handler)); - InstallExtension(has_new_note.get(), profile()); + scoped_refptr extension = + CreateAndInstallLockScreenApp(kNewNoteId, kName, profile()); - // Only the "new_note" extension is returned from GetAvailableApps. - std::vector apps = helper()->GetAvailableApps(profile()); - ASSERT_EQ(1u, apps.size()); - EXPECT_EQ(GetAppString(kNewNoteId, kName, false /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(apps[0])); + EXPECT_TRUE(AvailableAppsMatch( + profile(), {{kName, kNewNoteId, false /*preferred*/, + NoteTakingLockScreenSupport::kNotSupported}})); } TEST_P(NoteTakingHelperTest, WhitelistedAndCustomAppsShowOnlyOnce) { @@ -794,12 +762,10 @@ TEST_P(NoteTakingHelperTest, WhitelistedAndCustomAppsShowOnlyOnce) { .Build()); InstallExtension(extension.get(), profile()); - std::vector apps = helper()->GetAvailableApps(profile()); - ASSERT_EQ(1u, apps.size()); - EXPECT_EQ(GetAppString(NoteTakingHelper::kProdKeepExtensionId, "Keep", - false /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(apps[0])); + EXPECT_TRUE(AvailableAppsMatch( + profile(), + {{"Keep", NoteTakingHelper::kProdKeepExtensionId, false /*preferred*/, + NoteTakingLockScreenSupport::kNotSupported}})); } TEST_P(NoteTakingHelperTest, LaunchChromeApp) { @@ -944,29 +910,20 @@ TEST_P(NoteTakingHelperTest, ListAndroidApps) { EXPECT_TRUE(helper()->play_store_enabled()); EXPECT_TRUE(helper()->android_apps_received()); EXPECT_TRUE(helper()->IsAppAvailable(profile())); - std::vector apps = helper()->GetAvailableApps(profile()); - ASSERT_EQ(2u, apps.size()); - EXPECT_EQ(GetAppString(kPackage1, kName1, false /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(apps[0])); - EXPECT_EQ(GetAppString(kPackage2, kName2, false /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(apps[1])); + EXPECT_TRUE(AvailableAppsMatch( + profile(), {{kName1, kPackage1, false /*preferred*/, + NoteTakingLockScreenSupport::kNotSupported}, + {kName2, kPackage2, false /*preferred*/, + NoteTakingLockScreenSupport::kNotSupported}})); helper()->SetPreferredApp(profile(), kPackage1); - apps = helper()->GetAvailableApps(profile()); - ASSERT_EQ(2u, apps.size()); - EXPECT_EQ(GetAppString(kPackage1, kName1, true /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(apps[0])); - EXPECT_EQ(GetAppString(kPackage2, kName2, false /* preferred */, - NoteTakingLockScreenSupport::kNotSupported), - GetAppString(apps[1])); - - std::unique_ptr preferred_info = - helper()->GetPreferredChromeAppInfo(profile()); - EXPECT_FALSE(preferred_info); + EXPECT_TRUE(AvailableAppsMatch( + profile(), {{kName1, kPackage1, true /*preferred*/, + NoteTakingLockScreenSupport::kNotSupported}, + {kName2, kPackage2, false /*preferred*/, + NoteTakingLockScreenSupport::kNotSupported}})); + EXPECT_FALSE(helper()->GetPreferredChromeAppInfo(profile())); // TODO(victorhsieh): Opt-out on Persistent ARC is special. Skip until // implemented. @@ -1157,4 +1114,389 @@ TEST_P(NoteTakingHelperTest, NotifyObserverAboutChromeApps) { profile_manager_->DeleteTestingProfile(kSecondProfileName); } +TEST_P(NoteTakingHelperTest, NotifyObserverAboutPreferredAppChanges) { + Init(ENABLE_PALETTE); + TestObserver observer; + + scoped_refptr prod_keep_extension = + CreateExtension(NoteTakingHelper::kProdKeepExtensionId, "Keep"); + InstallExtension(prod_keep_extension.get(), profile()); + + scoped_refptr dev_keep_extension = + CreateExtension(NoteTakingHelper::kDevKeepExtensionId, "Keep"); + InstallExtension(dev_keep_extension.get(), profile()); + + ASSERT_TRUE(observer.preferred_app_updates().empty()); + + // Observers should be notified when preferred app is set. + helper()->SetPreferredApp(profile(), prod_keep_extension->id()); + EXPECT_EQ(std::vector{profile()}, observer.preferred_app_updates()); + observer.clear_preferred_app_updates(); + + // If the preferred app is not changed, observers should not be notified. + helper()->SetPreferredApp(profile(), prod_keep_extension->id()); + EXPECT_TRUE(observer.preferred_app_updates().empty()); + + // Observers should be notified when preferred app is changed. + helper()->SetPreferredApp(profile(), dev_keep_extension->id()); + EXPECT_EQ(std::vector{profile()}, observer.preferred_app_updates()); + observer.clear_preferred_app_updates(); + + // Observers should be notified when preferred app is cleared. + helper()->SetPreferredApp(profile(), ""); + EXPECT_EQ(std::vector{profile()}, observer.preferred_app_updates()); + observer.clear_preferred_app_updates(); + + // No change to preferred app. + helper()->SetPreferredApp(profile(), ""); + EXPECT_TRUE(observer.preferred_app_updates().empty()); + + // Initialize secondary profile with a test app. + const std::string kSecondProfileName = "second-profile"; + TestingProfile* second_profile = + profile_manager_->CreateTestingProfile(kSecondProfileName); + InitExtensionService(second_profile); + scoped_refptr + second_profile_prod_keep_extension = + CreateExtension(NoteTakingHelper::kProdKeepExtensionId, "Keep"); + InstallExtension(second_profile_prod_keep_extension.get(), second_profile); + + // Verify that observers are called with the scondary profile if the secondary + // profile preferred app changes. + helper()->SetPreferredApp(second_profile, + second_profile_prod_keep_extension->id()); + EXPECT_EQ(std::vector{second_profile}, + observer.preferred_app_updates()); + observer.clear_preferred_app_updates(); + + // Clearing preferred app in secondary ptofile should fire observers with the + // secondary profile. + helper()->SetPreferredApp(second_profile, ""); + EXPECT_EQ(std::vector{second_profile}, + observer.preferred_app_updates()); + observer.clear_preferred_app_updates(); + + profile_manager_->DeleteTestingProfile(kSecondProfileName); +} + +TEST_P(NoteTakingHelperTest, + NotifyObserverAboutPreferredAppLockScreenSupportChanges) { + Init(ENABLE_PALETTE | ENABLE_LOCK_SCREEN_APPS); + TestObserver observer; + + scoped_refptr dev_extension = + CreateAndInstallLockScreenApp(NoteTakingHelper::kDevKeepExtensionId, + kDevKeepAppName, profile()); + + scoped_refptr prod_extension = + CreateExtension(NoteTakingHelper::kProdKeepExtensionId, "Keep"); + InstallExtension(prod_extension.get(), profile()); + + ASSERT_TRUE(observer.preferred_app_updates().empty()); + + // Set the app that supports lock screen note taking as preferred. + helper()->SetPreferredApp(profile(), dev_extension->id()); + EXPECT_EQ(std::vector{profile()}, observer.preferred_app_updates()); + observer.clear_preferred_app_updates(); + + // Enable the preferred app on the lock screen. + EXPECT_TRUE(helper()->SetPreferredAppEnabledOnLockScreen(profile(), true)); + EXPECT_EQ(std::vector{profile()}, observer.preferred_app_updates()); + observer.clear_preferred_app_updates(); + + // Enabling lock screen support for already enabled app should be no-op. + EXPECT_FALSE(helper()->SetPreferredAppEnabledOnLockScreen(profile(), true)); + EXPECT_TRUE(observer.preferred_app_updates().empty()); + + // Change the state of the preferred app - it should succeed, and a + // notification should be fired. + EXPECT_TRUE(helper()->SetPreferredAppEnabledOnLockScreen(profile(), false)); + EXPECT_EQ(std::vector{profile()}, observer.preferred_app_updates()); + observer.clear_preferred_app_updates(); + + // No-op, becuase the preferred app state is not changing. + EXPECT_FALSE(helper()->SetPreferredAppEnabledOnLockScreen(profile(), false)); + EXPECT_TRUE(observer.preferred_app_updates().empty()); + + // Set an app that does not support lock screen as primary. + helper()->SetPreferredApp(profile(), prod_extension->id()); + EXPECT_EQ(std::vector{profile()}, observer.preferred_app_updates()); + observer.clear_preferred_app_updates(); + + // Chaning state for an app that does not support lock screen note taking + // should be no-op. + EXPECT_FALSE(helper()->SetPreferredAppEnabledOnLockScreen(profile(), true)); + EXPECT_TRUE(observer.preferred_app_updates().empty()); +} + +TEST_P(NoteTakingHelperTest, SetAppEnabledOnLockScreen) { + Init(ENABLE_PALETTE | ENABLE_LOCK_SCREEN_APPS); + + TestObserver observer; + + scoped_refptr dev_app = + CreateAndInstallLockScreenApp(NoteTakingHelper::kDevKeepExtensionId, + kDevKeepAppName, profile()); + scoped_refptr prod_app = + CreateAndInstallLockScreenApp(NoteTakingHelper::kProdKeepExtensionId, + kProdKeepAppName, profile()); + const std::string kUnsupportedAppName = "App name"; + const extensions::ExtensionId kUnsupportedAppId = + crx_file::id_util::GenerateId("a"); + scoped_refptr unsupported_app = + CreateAndInstallLockScreenAppWithPermissions( + kUnsupportedAppId, kUnsupportedAppName, nullptr, profile()); + + // Enabling preffered app on lock screen should fail if there is no preferred + // app. + EXPECT_FALSE(helper()->SetPreferredAppEnabledOnLockScreen(profile(), true)); + + helper()->SetPreferredApp(profile(), prod_app->id()); + + // Setting preferred app should fire observers. + EXPECT_EQ(std::vector{profile()}, observer.preferred_app_updates()); + observer.clear_preferred_app_updates(); + + // Verify that no app is enabled on lock screen. + EXPECT_TRUE(AvailableAppsMatch( + profile(), + {{kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + false /*preferred*/, NoteTakingLockScreenSupport::kSupported}, + {kProdKeepAppName, NoteTakingHelper::kProdKeepExtensionId, + true /*preferred*/, NoteTakingLockScreenSupport::kSupported}, + {kUnsupportedAppName, kUnsupportedAppId, false /*preferred*/, + NoteTakingLockScreenSupport::kNotSupported}})); + + // Enabling preferred app on lock screen should succeed when the app supports + // lock screen.. + EXPECT_TRUE(helper()->SetPreferredAppEnabledOnLockScreen(profile(), true)); + + // Preferred app pref changed, observers should be notified. + EXPECT_EQ(std::vector{profile()}, observer.preferred_app_updates()); + observer.clear_preferred_app_updates(); + + // Verify dev app is enabled on lock screen. + EXPECT_TRUE(AvailableAppsMatch( + profile(), {{kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + false /*preferred*/, NoteTakingLockScreenSupport::kEnabled}, + {kProdKeepAppName, NoteTakingHelper::kProdKeepExtensionId, + true /*preferred*/, NoteTakingLockScreenSupport::kEnabled}, + {kUnsupportedAppName, kUnsupportedAppId, false /*preferred*/, + NoteTakingLockScreenSupport::kNotSupported}})); + + // Whitelist prod app by policy. + profile_prefs_->SetManagedPref( + prefs::kNoteTakingAppsLockScreenWhitelist, + extensions::ListBuilder().Append(prod_app->id()).Build()); + + // The preferred app's status hasn't changed, so the observers can remain + // agnostic of the policy change. + EXPECT_TRUE(observer.preferred_app_updates().empty()); + + EXPECT_TRUE(AvailableAppsMatch( + profile(), + {{kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + false /*preferred*/, NoteTakingLockScreenSupport::kNotAllowedByPolicy}, + {kProdKeepAppName, NoteTakingHelper::kProdKeepExtensionId, + true /*preferred*/, NoteTakingLockScreenSupport::kEnabled}, + {kUnsupportedAppName, kUnsupportedAppId, false /*preferred*/, + NoteTakingLockScreenSupport::kNotSupported}})); + + // Change whitelist so only dev app is whitelisted. + profile_prefs_->SetManagedPref( + prefs::kNoteTakingAppsLockScreenWhitelist, + extensions::ListBuilder().Append(dev_app->id()).Build()); + + // The preferred app status changed, so observers are expected to be notified. + EXPECT_EQ(std::vector{profile()}, observer.preferred_app_updates()); + observer.clear_preferred_app_updates(); + + // Preferred app is not enabled on lock screen - chaning the lock screen + // pref should fail. + EXPECT_FALSE(helper()->SetPreferredAppEnabledOnLockScreen(profile(), false)); + EXPECT_TRUE(observer.preferred_app_updates().empty()); + + EXPECT_TRUE(AvailableAppsMatch( + profile(), + {{kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + false /*preferred*/, NoteTakingLockScreenSupport::kEnabled}, + {kProdKeepAppName, NoteTakingHelper::kProdKeepExtensionId, + true /*preferred*/, NoteTakingLockScreenSupport::kNotAllowedByPolicy}, + {kUnsupportedAppName, kUnsupportedAppId, false /*preferred*/, + NoteTakingLockScreenSupport::kNotSupported}})); + + // Switch preferred note taking app to one that does not support lock screen. + helper()->SetPreferredApp(profile(), unsupported_app->id()); + + EXPECT_EQ(std::vector{profile()}, observer.preferred_app_updates()); + observer.clear_preferred_app_updates(); + + // Policy with an empty whitelist - this should disallow all apps from the + // lock screen. + profile_prefs_->SetManagedPref(prefs::kNoteTakingAppsLockScreenWhitelist, + base::MakeUnique()); + + // Preferred app changed notification is not expected if the preferred app is + // not supported on lock screen. + EXPECT_TRUE(observer.preferred_app_updates().empty()); + + EXPECT_TRUE(AvailableAppsMatch( + profile(), + {{kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + false /*preferred*/, NoteTakingLockScreenSupport::kNotAllowedByPolicy}, + {kProdKeepAppName, NoteTakingHelper::kProdKeepExtensionId, + false /*preferred*/, NoteTakingLockScreenSupport::kNotAllowedByPolicy}, + {kUnsupportedAppName, kUnsupportedAppId, true /*preferred*/, + NoteTakingLockScreenSupport::kNotSupported}})); + + UninstallExtension(dev_app.get(), profile()); + UninstallExtension(prod_app.get(), profile()); + UninstallExtension(unsupported_app.get(), profile()); + + profile_prefs_->RemoveManagedPref(prefs::kNoteTakingAppsLockScreenWhitelist); + // No preferred app installed, so no update notification. + EXPECT_TRUE(observer.preferred_app_updates().empty()); +} + +TEST_P(NoteTakingHelperTest, + UpdateLockScreenSupportStatusWhenWhitelistPolicyRemoved) { + Init(ENABLE_PALETTE | ENABLE_LOCK_SCREEN_APPS); + TestObserver observer; + + // Add test app, set it as preferred and enable it on lock screen. + scoped_refptr app = + CreateAndInstallLockScreenApp(NoteTakingHelper::kDevKeepExtensionId, + kDevKeepAppName, profile()); + helper()->SetPreferredApp(profile(), app->id()); + EXPECT_TRUE(helper()->SetPreferredAppEnabledOnLockScreen(profile(), true)); + observer.clear_preferred_app_updates(); + EXPECT_TRUE(AvailableAppsMatch( + profile(), + {{kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + true /*preferred*/, NoteTakingLockScreenSupport::kEnabled}})); + + // Policy with an empty whitelist - this should disallow test app from running + // on lock screen. + profile_prefs_->SetManagedPref(prefs::kNoteTakingAppsLockScreenWhitelist, + base::MakeUnique()); + + // Preferred app settings changed - observers should be notified. + EXPECT_EQ(std::vector{profile()}, observer.preferred_app_updates()); + observer.clear_preferred_app_updates(); + + // Verify the app is reported as not allowed by policy. + EXPECT_TRUE(AvailableAppsMatch( + profile(), {{kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + true /*preferred*/, + NoteTakingLockScreenSupport::kNotAllowedByPolicy}})); + + // Remove the whitelist policy - the preferred app should become enabled on + // lock screen again. + profile_prefs_->RemoveManagedPref(prefs::kNoteTakingAppsLockScreenWhitelist); + + EXPECT_EQ(std::vector{profile()}, observer.preferred_app_updates()); + observer.clear_preferred_app_updates(); + + EXPECT_TRUE(AvailableAppsMatch( + profile(), + {{kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + true /*preferred*/, NoteTakingLockScreenSupport::kEnabled}})); +} + +TEST_P(NoteTakingHelperTest, + NoObserverCallsIfPolicyChangesBeforeLockScreenStatusIsFetched) { + Init(ENABLE_PALETTE | ENABLE_LOCK_SCREEN_APPS); + TestObserver observer; + + scoped_refptr app = + CreateAndInstallLockScreenApp(NoteTakingHelper::kDevKeepExtensionId, + kDevKeepAppName, profile()); + + profile_prefs_->SetManagedPref(prefs::kNoteTakingAppsLockScreenWhitelist, + base::MakeUnique()); + // Verify that observers are not notified of preferred app change if preferred + // app is not set when whitelist policy changes. + EXPECT_TRUE(observer.preferred_app_updates().empty()); + + // Set test app as preferred note taking app. + helper()->SetPreferredApp(profile(), app->id()); + EXPECT_EQ(std::vector{profile()}, observer.preferred_app_updates()); + observer.clear_preferred_app_updates(); + + // Changing policy before the app's lock screen availability has been reported + // to NoteTakingHelper clients is not expected to fire observers. + profile_prefs_->SetManagedPref( + prefs::kNoteTakingAppsLockScreenWhitelist, + extensions::ListBuilder().Append(app->id()).Build()); + EXPECT_TRUE(observer.preferred_app_updates().empty()); + + EXPECT_TRUE(AvailableAppsMatch( + profile(), + {{kDevKeepAppName, NoteTakingHelper::kDevKeepExtensionId, + true /*preferred*/, NoteTakingLockScreenSupport::kSupported}})); +} + +TEST_P(NoteTakingHelperTest, LockScreenSupportInSecondaryProfile) { + Init(ENABLE_PALETTE | ENABLE_LOCK_SCREEN_APPS); + TestObserver observer; + + // Initialize secondary profile. + auto prefs = base::MakeUnique(); + chrome::RegisterUserProfilePrefs(prefs->registry()); + sync_preferences::TestingPrefServiceSyncable* profile_prefs = prefs.get(); + const std::string kSecondProfileName = "second-profile"; + TestingProfile* second_profile = profile_manager_->CreateTestingProfile( + kSecondProfileName, std::move(prefs), base::ASCIIToUTF16("Test profile"), + 1 /*avatar_id*/, std::string() /*supervised_user_id*/, + TestingProfile::TestingFactories()); + InitExtensionService(second_profile); + + // Add test apps to secondary profile. + scoped_refptr prod_app = + CreateAndInstallLockScreenApp(NoteTakingHelper::kProdKeepExtensionId, + kProdKeepAppName, second_profile); + const std::string kUnsupportedAppName = "App name"; + const extensions::ExtensionId kUnsupportedAppId = + crx_file::id_util::GenerateId("a"); + scoped_refptr unsupported_app = + CreateAndInstallLockScreenAppWithPermissions( + kUnsupportedAppId, kUnsupportedAppName, nullptr, second_profile); + + // Setting preferred app should fire observers for secondary profile. + helper()->SetPreferredApp(second_profile, prod_app->id()); + EXPECT_EQ(std::vector{second_profile}, + observer.preferred_app_updates()); + observer.clear_preferred_app_updates(); + + // Even though prod app supports lock screen it should be reported as not + // supported in the secondary profile. + EXPECT_TRUE(AvailableAppsMatch( + second_profile, + {{kProdKeepAppName, NoteTakingHelper::kProdKeepExtensionId, + true /*preferred*/, NoteTakingLockScreenSupport::kNotSupported}, + {kUnsupportedAppName, kUnsupportedAppId, false /*preferred*/, + NoteTakingLockScreenSupport::kNotSupported}})); + + // Enabling an app on lock screen in secondary profile should fail. + EXPECT_FALSE(helper()->SetPreferredAppEnabledOnLockScreen(profile(), true)); + + // Policy with an empty whitelist. + profile_prefs->SetManagedPref(prefs::kNoteTakingAppsLockScreenWhitelist, + base::MakeUnique()); + + // Changing policy should not notify observers in secondary profile. + EXPECT_TRUE(observer.preferred_app_updates().empty()); + + EXPECT_TRUE(AvailableAppsMatch( + second_profile, + {{kProdKeepAppName, NoteTakingHelper::kProdKeepExtensionId, + true /*preferred*/, NoteTakingLockScreenSupport::kNotSupported}, + {kUnsupportedAppName, kUnsupportedAppId, false /*preferred*/, + NoteTakingLockScreenSupport::kNotSupported}})); + EXPECT_TRUE(PreferredAppMatches( + second_profile, + {kProdKeepAppName, NoteTakingHelper::kProdKeepExtensionId, + true /*preferred*/, NoteTakingLockScreenSupport::kNotSupported})); +} + } // namespace chromeos diff --git a/chrome/browser/chromeos/preferences.cc b/chrome/browser/chromeos/preferences.cc index cb8821ce0f2b..47ce274d2433 100644 --- a/chrome/browser/chromeos/preferences.cc +++ b/chrome/browser/chromeos/preferences.cc @@ -314,6 +314,7 @@ void Preferences::RegisterProfilePrefs( registry->RegisterStringPref(prefs::kNoteTakingAppId, std::string()); registry->RegisterBooleanPref(prefs::kNoteTakingAppEnabledOnLockScreen, false); + registry->RegisterListPref(prefs::kNoteTakingAppsLockScreenWhitelist); // We don't sync wake-on-wifi related prefs because they are device specific. registry->RegisterBooleanPref(prefs::kWakeOnWifiDarkConnect, true); diff --git a/chrome/browser/extensions/api/settings_private/prefs_util.cc b/chrome/browser/extensions/api/settings_private/prefs_util.cc index a0769f5198b3..4b9dd688d17d 100644 --- a/chrome/browser/extensions/api/settings_private/prefs_util.cc +++ b/chrome/browser/extensions/api/settings_private/prefs_util.cc @@ -298,8 +298,6 @@ const PrefsUtil::TypedPrefMap& PrefsUtil::GetWhitelistedKeys() { settings_private::PrefType::PREF_TYPE_BOOLEAN; (*s_whitelist)[::prefs::kLaunchPaletteOnEjectEvent] = settings_private::PrefType::PREF_TYPE_BOOLEAN; - (*s_whitelist)[::prefs::kNoteTakingAppEnabledOnLockScreen] = - settings_private::PrefType::PREF_TYPE_BOOLEAN; (*s_whitelist)[ash::prefs::kNightLightEnabled] = settings_private::PrefType::PREF_TYPE_BOOLEAN; (*s_whitelist)[ash::prefs::kNightLightTemperature] = diff --git a/chrome/browser/resources/settings/device_page/compiled_resources2.gyp b/chrome/browser/resources/settings/device_page/compiled_resources2.gyp index 97ba4520bea2..0b47015c536a 100644 --- a/chrome/browser/resources/settings/device_page/compiled_resources2.gyp +++ b/chrome/browser/resources/settings/device_page/compiled_resources2.gyp @@ -45,6 +45,7 @@ { 'target_name': 'stylus', 'dependencies': [ + '<(DEPTH)/ui/webui/resources/cr_elements/policy/compiled_resources2.gyp:cr_policy_indicator', '<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr', '../prefs/compiled_resources2.gyp:prefs_types', 'device_page_browser_proxy' diff --git a/chrome/browser/resources/settings/device_page/device_page_browser_proxy.js b/chrome/browser/resources/settings/device_page/device_page_browser_proxy.js index d84e6c061d2d..29d311905c12 100644 --- a/chrome/browser/resources/settings/device_page/device_page_browser_proxy.js +++ b/chrome/browser/resources/settings/device_page/device_page_browser_proxy.js @@ -66,11 +66,23 @@ settings.LidClosedBehavior = { */ settings.PowerManagementSettings; +/** + * A note app's availability for running as note handler app from lock screen. + * Mirrors chromeos::NoteTakingLockScreenSupport. + * @enum {number} + */ +settings.NoteAppLockScreenSupport = { + NOT_SUPPORTED: 0, + NOT_ALLOWED_BY_POLICY: 1, + SUPPORTED: 2, + ENABLED: 3 +}; + /** * @typedef {{name:string, * value:string, * preferred:boolean, - * supportsLockScreen: boolean}} + * lockScreenSupport: settings.NoteAppLockScreenSupport}} */ settings.NoteAppInfo; @@ -146,6 +158,14 @@ cr.define('settings', function() { * |onNoteTakingAppsUpdated| callback. */ setPreferredNoteTakingApp(appId) {} + + /** + * Sets whether the preferred note taking app should be enabled to run as a + * lock screen note action handler. + * @param {boolean} enabled Whether the app should be enabled to handle note + * actions from the lock screen. + */ + setPreferredNoteTakingAppEnabledOnLockScreen(enabled) {} } /** @@ -224,6 +244,11 @@ cr.define('settings', function() { setPreferredNoteTakingApp(appId) { chrome.send('setPreferredNoteTakingApp', [appId]); } + + /** @override */ + setPreferredNoteTakingAppEnabledOnLockScreen(enabled) { + chrome.send('setPreferredNoteTakingAppEnabledOnLockScreen', [enabled]); + } } cr.addSingletonGetter(DevicePageBrowserProxyImpl); diff --git a/chrome/browser/resources/settings/device_page/stylus.html b/chrome/browser/resources/settings/device_page/stylus.html index 7f2e4a9d539b..a14b7542290a 100644 --- a/chrome/browser/resources/settings/device_page/stylus.html +++ b/chrome/browser/resources/settings/device_page/stylus.html @@ -23,6 +23,10 @@ margin-left: 12px; @apply(--cr-icon-height-width); } + + cr-policy-indicator { + padding: 0 var(--cr-controlled-by-spacing); + }
@@ -71,11 +75,24 @@ diff --git a/chrome/browser/resources/settings/device_page/stylus.js b/chrome/browser/resources/settings/device_page/stylus.js index d80f250db716..e35cb4be2b4b 100644 --- a/chrome/browser/resources/settings/device_page/stylus.js +++ b/chrome/browser/resources/settings/device_page/stylus.js @@ -20,6 +20,18 @@ Polymer({ notify: true, }, + /** + * Policy indicator type for user policy - used for policy indicator UI + * shown when an app that is not allowed to run on lock screen by policy is + * selected. + * @const {CrPolicyIndicatorType} + * @private + */ + userPolicyIndicator_: { + type: String, + value: CrPolicyIndicatorType.USER_POLICY, + }, + /** * Note taking apps the user can pick between. * @private {Array} @@ -50,8 +62,37 @@ Polymer({ }, }, + /** + * @return {boolean} Whether note taking from the lock screen is supported + * by the selected note-taking app. + * @private + */ supportsLockScreen_: function() { - return this.selectedApp_ && this.selectedApp_.supportsLockScreen; + return !!this.selectedApp_ && + this.selectedApp_.lockScreenSupport != + settings.NoteAppLockScreenSupport.NOT_SUPPORTED; + }, + + /** + * @return {boolean} Whether the selected app is disallowed to handle note + * actions from lock screen as a result of a user policy. + * @private + */ + disallowedOnLockScreenByPolicy_: function() { + return !!this.selectedApp_ && + this.selectedApp_.lockScreenSupport == + settings.NoteAppLockScreenSupport.NOT_ALLOWED_BY_POLICY; + }, + + /** + * @return {boolean} Whether the selected app is enabled as a note action + * handler on the lock screen. + * @private + */ + lockScreenSupportEnabled_: function() { + return !!this.selectedApp_ && + this.selectedApp_.lockScreenSupport == + settings.NoteAppLockScreenSupport.ENABLED; }, /** @private {?settings.DevicePageBrowserProxy} */ @@ -82,6 +123,25 @@ Polymer({ null; }, + /** + * Toggles whether the selected app is enabled as a note action handler on + * the lock screen. + * @private + */ + toggleLockScreenSupport_: function() { + assert(!!this.selectedApp_); + if (this.selectedApp_.lockScreenSupport != + settings.NoteAppLockScreenSupport.ENABLED && + this.selectedApp_.lockScreenSupport != + settings.NoteAppLockScreenSupport.SUPPORTED) { + return; + } + + this.browserProxy_.setPreferredNoteTakingAppEnabledOnLockScreen( + this.selectedApp_.lockScreenSupport == + settings.NoteAppLockScreenSupport.SUPPORTED); + }, + /** @private */ onSelectedAppChanged_: function() { var app = this.findApp_(this.$.menu.value); diff --git a/chrome/browser/ui/webui/options/chromeos/options_stylus_handler.cc b/chrome/browser/ui/webui/options/chromeos/options_stylus_handler.cc index 8d2c9a2c80e0..e705e1f67484 100644 --- a/chrome/browser/ui/webui/options/chromeos/options_stylus_handler.cc +++ b/chrome/browser/ui/webui/options/chromeos/options_stylus_handler.cc @@ -84,6 +84,11 @@ void OptionsStylusHandler::OnAvailableNoteTakingAppsUpdated() { UpdateNoteTakingApps(); } +void OptionsStylusHandler::OnPreferredNoteTakingAppUpdated(Profile* profile) { + if (Profile::FromWebUI(web_ui()) == profile) + UpdateNoteTakingApps(); +} + void OptionsStylusHandler::OnDeviceListsComplete() { SendHasStylus(); } diff --git a/chrome/browser/ui/webui/options/chromeos/options_stylus_handler.h b/chrome/browser/ui/webui/options/chromeos/options_stylus_handler.h index 6e729c69959f..3a51832affee 100644 --- a/chrome/browser/ui/webui/options/chromeos/options_stylus_handler.h +++ b/chrome/browser/ui/webui/options/chromeos/options_stylus_handler.h @@ -14,6 +14,8 @@ #include "chrome/browser/ui/webui/options/options_ui.h" #include "ui/events/devices/input_device_event_observer.h" +class Profile; + namespace base { class ListValue; } // namespace base @@ -36,6 +38,7 @@ class OptionsStylusHandler : public ::options::OptionsPageUIHandler, // NoteTakingHelper::Observer implementation. void OnAvailableNoteTakingAppsUpdated() override; + void OnPreferredNoteTakingAppUpdated(Profile* profile) override; // ui::InputDeviceEventObserver implementation: void OnDeviceListsComplete() override; diff --git a/chrome/browser/ui/webui/settings/chromeos/device_stylus_handler.cc b/chrome/browser/ui/webui/settings/chromeos/device_stylus_handler.cc index 7f7ba9c3b47b..0d356464b378 100644 --- a/chrome/browser/ui/webui/settings/chromeos/device_stylus_handler.cc +++ b/chrome/browser/ui/webui/settings/chromeos/device_stylus_handler.cc @@ -24,7 +24,7 @@ namespace { constexpr char kAppNameKey[] = "name"; constexpr char kAppIdKey[] = "value"; constexpr char kAppPreferredKey[] = "preferred"; -constexpr char kAppLockScreenSupportKey[] = "supportsLockScreen"; +constexpr char kAppLockScreenSupportKey[] = "lockScreenSupport"; } // namespace @@ -51,6 +51,10 @@ void StylusHandler::RegisterMessages() { "setPreferredNoteTakingApp", base::Bind(&StylusHandler::SetPreferredNoteTakingApp, base::Unretained(this))); + web_ui()->RegisterMessageCallback( + "setPreferredNoteTakingAppEnabledOnLockScreen", + base::Bind(&StylusHandler::SetPreferredNoteTakingAppEnabledOnLockScreen, + base::Unretained(this))); web_ui()->RegisterMessageCallback( "showPlayStoreApps", base::Bind(&StylusHandler::ShowPlayStoreApps, base::Unretained(this))); @@ -60,6 +64,11 @@ void StylusHandler::OnAvailableNoteTakingAppsUpdated() { UpdateNoteTakingApps(); } +void StylusHandler::OnPreferredNoteTakingAppUpdated(Profile* profile) { + if (Profile::FromWebUI(web_ui()) == profile) + UpdateNoteTakingApps(); +} + void StylusHandler::OnDeviceListsComplete() { SendHasStylus(); } @@ -82,9 +91,8 @@ void StylusHandler::UpdateNoteTakingApps() { dict->SetString(kAppNameKey, info.name); dict->SetString(kAppIdKey, info.app_id); dict->SetBoolean(kAppPreferredKey, info.preferred); - dict->SetBoolean(kAppLockScreenSupportKey, - info.lock_screen_support != - NoteTakingLockScreenSupport::kNotSupported); + dict->SetInteger(kAppLockScreenSupportKey, + static_cast(info.lock_screen_support)); apps_list.Append(std::move(dict)); note_taking_app_ids_.insert(info.app_id); @@ -115,6 +123,15 @@ void StylusHandler::SetPreferredNoteTakingApp(const base::ListValue* args) { app_id); } +void StylusHandler::SetPreferredNoteTakingAppEnabledOnLockScreen( + const base::ListValue* args) { + bool enabled = false; + CHECK(args->GetBoolean(0, &enabled)); + + NoteTakingHelper::Get()->SetPreferredAppEnabledOnLockScreen( + Profile::FromWebUI(web_ui()), enabled); +} + void StylusHandler::HandleInitialize(const base::ListValue* args) { if (ui::InputDeviceManager::GetInstance()->AreDeviceListsComplete()) SendHasStylus(); diff --git a/chrome/browser/ui/webui/settings/chromeos/device_stylus_handler.h b/chrome/browser/ui/webui/settings/chromeos/device_stylus_handler.h index b8cd2567f994..ac52790f5ad0 100644 --- a/chrome/browser/ui/webui/settings/chromeos/device_stylus_handler.h +++ b/chrome/browser/ui/webui/settings/chromeos/device_stylus_handler.h @@ -6,6 +6,7 @@ #define CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_DEVICE_STYLUS_HANDLER_H_ #include +#include #include "base/macros.h" #include "chrome/browser/chromeos/note_taking_helper.h" @@ -34,6 +35,7 @@ class StylusHandler : public ::settings::SettingsPageUIHandler, // chromeos::NoteTakingHelper::Observer implementation. void OnAvailableNoteTakingAppsUpdated() override; + void OnPreferredNoteTakingAppUpdated(Profile* profile) override; // ui::InputDeviceObserver: void OnDeviceListsComplete() override; @@ -42,6 +44,8 @@ class StylusHandler : public ::settings::SettingsPageUIHandler, void UpdateNoteTakingApps(); void RequestApps(const base::ListValue* unused_args); void SetPreferredNoteTakingApp(const base::ListValue* args); + void SetPreferredNoteTakingAppEnabledOnLockScreen( + const base::ListValue* args); // Called by JS to request a |SendHasStylus| call. void HandleInitialize(const base::ListValue* args); diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc index e964e4734194..490a48e53164 100644 --- a/chrome/common/pref_names.cc +++ b/chrome/common/pref_names.cc @@ -727,6 +727,12 @@ const char kNoteTakingAppId[] = "settings.note_taking_app_id"; const char kNoteTakingAppEnabledOnLockScreen[] = "settings.note_taking_app_enabled_on_lock_screen"; +// List of note taking aps that can be enabled to run on the lock screen. +// The intended usage is to whitelist the set of apps that the user can enable +// to run on lock screen, not to actually enable the apps to run on lock screen. +const char kNoteTakingAppsLockScreenWhitelist[] = + "settings.note_taking_apps_lock_screen_whitelist"; + // A boolean pref indicating whether user activity has been observed in the // current session already. The pref is used to restore information about user // activity after browser crashes. diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h index 98fd980920d8..859e750bcca6 100644 --- a/chrome/common/pref_names.h +++ b/chrome/common/pref_names.h @@ -264,6 +264,7 @@ extern const char kEnableStylusTools[]; extern const char kLaunchPaletteOnEjectEvent[]; extern const char kNoteTakingAppId[]; extern const char kNoteTakingAppEnabledOnLockScreen[]; +extern const char kNoteTakingAppsLockScreenWhitelist[]; extern const char kSessionUserActivitySeen[]; extern const char kSessionStartTime[]; extern const char kSessionLengthLimit[]; diff --git a/chrome/test/data/webui/settings/device_page_tests.js b/chrome/test/data/webui/settings/device_page_tests.js index 7f41ae54becf..ea85cf9f2c35 100644 --- a/chrome/test/data/webui/settings/device_page_tests.js +++ b/chrome/test/data/webui/settings/device_page_tests.js @@ -21,9 +21,13 @@ cr.define('device_page_tests', function() { this.keyboardShortcutsOverlayShown_ = 0; this.updatePowerStatusCalled_ = 0; this.requestPowerManagementSettingsCalled_ = 0; - this.onNoteTakingAppsUpdated_ = null; this.requestNoteTakingApps_ = 0; - this.setPreferredNoteTakingApp_ = ''; + this.onNoteTakingAppsUpdated_ = null; + + this.androidAppsReceived_ = false; + this.noteTakingApps_ = []; + this.setPreferredAppCount_ = 0; + this.setAppOnLockScreenCount_ = 0; } TestDevicePageBrowserProxy.prototype = { @@ -93,8 +97,113 @@ cr.define('device_page_tests', function() { /** @override */ setPreferredNoteTakingApp: function(appId) { - this.setPreferredNoteTakingApp_ = appId; + ++this.setPreferredAppCount_; + + var changed = false; + this.noteTakingApps_.forEach(function(app) { + changed = changed || app.preferred != (app.value == appId); + app.preferred = app.value == appId; + }); + + if (changed) + this.scheduleLockScreenAppsUpdated_(); + }, + + /** @override */ + setPreferredNoteTakingAppEnabledOnLockScreen: function(enabled) { + ++this.setAppOnLockScreenCount_; + + this.noteTakingApps_.forEach(function(app) { + if (enabled) { + if (app.preferred) { + assertEquals(settings.NoteAppLockScreenSupport.SUPPORTED, + app.lockScreenSupport); + } + if (app.lockScreenSupport == + settings.NoteAppLockScreenSupport.SUPPORTED) { + app.lockScreenSupport = settings.NoteAppLockScreenSupport.ENABLED; + } + } else { + if (app.preferred) { + assertEquals(settings.NoteAppLockScreenSupport.ENABLED, + app.lockScreenSupport); + } + if (app.lockScreenSupport == + settings.NoteAppLockScreenSupport.ENABLED) { + app.lockScreenSupport = settings.NoteAppLockScreenSupport.SUPPORTED; + } + } + }); + + this.scheduleLockScreenAppsUpdated_(); + }, + + // Test interface: + /** + * Sets whether the app list contains Android apps. + * @param {boolean} Whether the list of Android note-taking apps was + * received. + */ + setAndroidAppsReceived: function(received) { + this.androidAppsReceived_ = received; + + this.scheduleLockScreenAppsUpdated_(); + }, + + /** + * @return {string} App id of the app currently selected as preferred. + */ + getPreferredNoteTakingAppId: function() { + var app = this.noteTakingApps_.find(function(existing) { + return existing.preferred; + }); + + return app ? app.value : ''; + }, + + /** + * @return {settings.NoteAppLockScreenSupport | undefined} The lock screen + * support state of the app currently selected as preferred. + */ + getPreferredAppLockScreenState: function() { + var app = this.noteTakingApps_.find(function(existing) { + return existing.preferred; + }); + + return app ? app.lockScreenSupport : undefined; + }, + + /** + * Sets the current list of known note taking apps. + * @param {Array} The list of apps to set. + */ + setNoteTakingApps: function(apps) { + this.noteTakingApps_ = apps; + this.scheduleLockScreenAppsUpdated_(); + }, + + /** + * Adds an app to the list of known note-taking apps. + * @param {!settings.NoteAppInfo} + */ + addNoteTakingApp: function(app) { + assert(!this.noteTakingApps_.find(function(existing) { + return existing.value === app.value; + })); + + this.noteTakingApps_.push(app); + this.scheduleLockScreenAppsUpdated_(); }, + + /** + * Invokes the registered note taking apps update callback. + * @private + */ + scheduleLockScreenAppsUpdated_: function() { + this.onNoteTakingAppsUpdated_(this.noteTakingApps_.map(function(app) { + return Object.assign({}, app); + }), !this.androidAppsReceived_); + } }; function getFakePrefs() { @@ -219,11 +328,6 @@ cr.define('device_page_tests', function() { type: chrome.settingsPrivate.PrefType.NUMBER, value: 500, }, - }, - note_taking_app_enabled_on_lock_screen: { - key: 'settings.note_taking_app_enabled_on_lock_screen', - type: chrome.settingsPrivate.PrefType.BOOLEAN, - value: false } } }; @@ -946,6 +1050,8 @@ cr.define('device_page_tests', function() { var waitingDiv; var selectAppDiv; + // Shorthand for settings.NoteAppLockScreenSupport. + var LockScreenSupport; suiteSetup(function() { // Always show stylus settings. @@ -963,20 +1069,20 @@ cr.define('device_page_tests', function() { noAppsDiv = assert(page.$$('#no-apps')); waitingDiv = assert(page.$$('#waiting')); selectAppDiv = assert(page.$$('#select-app')); + LockScreenSupport = settings.NoteAppLockScreenSupport; assertEquals(1, browserProxy.requestNoteTakingApps_); - assertEquals('', browserProxy.setPreferredNoteTakingApp_); assert(browserProxy.onNoteTakingAppsUpdated_); }); }); // Helper function to allocate a note app entry. - function entry(name, value, preferred, supportsLockScreen) { + function entry(name, value, preferred, lockScreenSupport) { return { name: name, value: value, preferred: preferred, - supportsLockScreen: supportsLockScreen + lockScreenSupport: lockScreenSupport }; } @@ -987,6 +1093,14 @@ cr.define('device_page_tests', function() { return stylusPage.$$('#enable-app-on-lock-screen-toggle'); } + function enableAppOnLockScreenPolicyIndicator() { + return stylusPage.$$("#enable-app-on-lock-screen-policy-indicator"); + } + + function enableAppOnLockScreenToggleLabel() { + return stylusPage.$$('#lock-screen-toggle-label'); + } + /** * @param {Element|undefined} element */ @@ -994,95 +1108,96 @@ cr.define('device_page_tests', function() { return !!element && element.offsetWidth > 0 && element.offsetHeight > 0; } - test('initial app choice selector value', function() { + test('choose first app if no preferred ones', function() { // Selector chooses the first value in list if there is no preferred // value set. - browserProxy.onNoteTakingAppsUpdated_([ - entry('n1', 'v1', false, false), - entry('n2', 'v2', false, false) - ], false); + browserProxy.setNoteTakingApps([ + entry('n1', 'v1', false, LockScreenSupport.NOT_SUPPORTED), + entry('n2', 'v2', false, LockScreenSupport.NOT_SUPPORTED) + ]); Polymer.dom.flush(); assertEquals('v1', appSelector.value); + }); + test('choose prefered app if exists', function() { // Selector chooses the preferred value if set. - browserProxy.onNoteTakingAppsUpdated_([ - entry('n1', 'v1', false, false), - entry('n2', 'v2', true, false) - ], false); + browserProxy.setNoteTakingApps([ + entry('n1', 'v1', false, LockScreenSupport.NOT_SUPPORTED), + entry('n2', 'v2', true, LockScreenSupport.NOT_SUPPORTED) + ]); Polymer.dom.flush(); assertEquals('v2', appSelector.value); }); test('change preferred app', function() { // Load app list. - browserProxy.onNoteTakingAppsUpdated_([ - entry('n1', 'v1', false, false), - entry('n2', 'v2', true, false) - ], false); + browserProxy.setNoteTakingApps([ + entry('n1', 'v1', false, LockScreenSupport.NOT_SUPPORTED), + entry('n2', 'v2', true, LockScreenSupport.NOT_SUPPORTED) + ]); Polymer.dom.flush(); - assertEquals('', browserProxy.setPreferredNoteTakingApp_); + assertEquals(0, browserProxy.setPreferredAppCount_); + assertEquals('v2', browserProxy.getPreferredNoteTakingAppId()); // Update select element to new value, verify browser proxy is called. appSelector.value = 'v1'; stylusPage.onSelectedAppChanged_(); - assertEquals('v1', browserProxy.setPreferredNoteTakingApp_); + assertEquals(1, browserProxy.setPreferredAppCount_); + assertEquals('v1', browserProxy.getPreferredNoteTakingAppId()); }); test('preferred app does not change without interaction', function() { // Pass various types of data to page, verify the preferred note-app // does not change. - browserProxy.onNoteTakingAppsUpdated_([], false); + browserProxy.setNoteTakingApps([]); Polymer.dom.flush(); - assertEquals('', browserProxy.setPreferredNoteTakingApp_); + assertEquals('', browserProxy.getPreferredNoteTakingAppId()); browserProxy.onNoteTakingAppsUpdated_([], true); Polymer.dom.flush(); - assertEquals('', browserProxy.setPreferredNoteTakingApp_); - - browserProxy.onNoteTakingAppsUpdated_([ - entry('n', 'v', false, false) - ], true); - Polymer.dom.flush(); - assertEquals('', browserProxy.setPreferredNoteTakingApp_); + assertEquals('', browserProxy.getPreferredNoteTakingAppId()); - browserProxy.onNoteTakingAppsUpdated_([ - entry('n', 'v', false, false) - ], false); + browserProxy.addNoteTakingApp( + entry('n', 'v', false, LockScreenSupport.NOT_SUPPORTED)); Polymer.dom.flush(); - assertEquals('', browserProxy.setPreferredNoteTakingApp_); + assertEquals('', browserProxy.getPreferredNoteTakingAppId()); - browserProxy.onNoteTakingAppsUpdated_([ - entry('n1', 'v1', false, false), - entry('n2', 'v2', true, false) - ], false); + browserProxy.setNoteTakingApps([ + entry('n1', 'v1', false, LockScreenSupport.NOT_SUPPORTED), + entry('n2', 'v2', true, LockScreenSupport.NOT_SUPPORTED) + ]); Polymer.dom.flush(); - assertEquals('', browserProxy.setPreferredNoteTakingApp_); + assertEquals(0, browserProxy.setPreferredAppCount_); + assertEquals('v2', browserProxy.getPreferredNoteTakingAppId()); }); test('app-visibility', function() { // No apps available. - browserProxy.onNoteTakingAppsUpdated_([], false); + browserProxy.setNoteTakingApps([]); + assert(noAppsDiv.hidden); + assert(!waitingDiv.hidden); + assert(selectAppDiv.hidden); + + // Waiting for apps to finish loading. + browserProxy.setAndroidAppsReceived(true); assert(!noAppsDiv.hidden); assert(waitingDiv.hidden); assert(selectAppDiv.hidden); - // Waiting for apps to finish loading. - browserProxy.onNoteTakingAppsUpdated_([], true); + // Apps loaded, show selector. + browserProxy.addNoteTakingApp( + entry('n', 'v', false, LockScreenSupport.NOT_SUPPORTED)); assert(noAppsDiv.hidden); - assert(!waitingDiv.hidden); - assert(selectAppDiv.hidden); + assert(waitingDiv.hidden); + assert(!selectAppDiv.hidden); - browserProxy.onNoteTakingAppsUpdated_([ - entry('n', 'v', false, false) - ], true); + // Waiting for Android apps again. + browserProxy.setAndroidAppsReceived(false); assert(noAppsDiv.hidden); assert(!waitingDiv.hidden); assert(selectAppDiv.hidden); - // Apps loaded, show selector. - browserProxy.onNoteTakingAppsUpdated_([ - entry('n', 'v', false, false) - ], false); + browserProxy.setAndroidAppsReceived(true); assert(noAppsDiv.hidden); assert(waitingDiv.hidden); assert(!selectAppDiv.hidden); @@ -1090,88 +1205,110 @@ cr.define('device_page_tests', function() { test('enabled-on-lock-screen', function() { expectFalse(isVisible(enableAppOnLockScreenToggle())); + expectFalse(isVisible(enableAppOnLockScreenPolicyIndicator())); return new Promise(function(resolve) { // No apps available. - browserProxy.onNoteTakingAppsUpdated_([], false); + browserProxy.setNoteTakingApps([]); stylusPage.async(resolve); }).then(function() { Polymer.dom.flush(); expectFalse(isVisible(enableAppOnLockScreenToggle())); + expectFalse(isVisible(enableAppOnLockScreenPolicyIndicator())); // Single app which does not support lock screen note taking. - browserProxy.onNoteTakingAppsUpdated_([ - entry('n1', 'v1', false, false) - ], false); + browserProxy.addNoteTakingApp( + entry('n1', 'v1', true, LockScreenSupport.NOT_SUPPORTED)); return new Promise(function(resolve) {stylusPage.async(resolve);}); }).then(function() { Polymer.dom.flush(); expectFalse(isVisible(enableAppOnLockScreenToggle())); + expectFalse(isVisible(enableAppOnLockScreenPolicyIndicator())); // Add an app with lock screen support, but do not select it yet. - browserProxy.onNoteTakingAppsUpdated_([ - entry('n1', 'v1', false, false), - entry('n2', 'v2', false, true) - ], false); + browserProxy.addNoteTakingApp( + entry('n2', 'v2', false, LockScreenSupport.SUPPORTED)); return new Promise(function(resolve) { stylusPage.async(resolve); }); }).then(function() { Polymer.dom.flush(); expectFalse(isVisible(enableAppOnLockScreenToggle())); + expectFalse(isVisible(enableAppOnLockScreenPolicyIndicator())); // Select the app with lock screen app support. appSelector.value = 'v2'; stylusPage.onSelectedAppChanged_(); - assertEquals('v2', browserProxy.setPreferredNoteTakingApp_); + assertEquals(1, browserProxy.setPreferredAppCount_); + assertEquals('v2', browserProxy.getPreferredNoteTakingAppId()); + return new Promise(function(resolve) { stylusPage.async(resolve); }); + }).then(function() { Polymer.dom.flush(); + expectFalse(isVisible(enableAppOnLockScreenPolicyIndicator())); assert(isVisible(enableAppOnLockScreenToggle())); expectFalse(enableAppOnLockScreenToggle().checked); - devicePage.set( - 'prefs.settings.note_taking_app_enabled_on_lock_screen.value', - true); + // Preferred app updated to be enabled on lock screen. + browserProxy.setNoteTakingApps([ + entry('n1', 'v1', false, LockScreenSupport.NOT_SUPPORTED), + entry('n2', 'v2', true, LockScreenSupport.ENABLED) + ]); + return new Promise(function(resolve) { stylusPage.async(resolve); }); + }).then(function() { Polymer.dom.flush(); + expectFalse(isVisible(enableAppOnLockScreenPolicyIndicator())); assert(isVisible(enableAppOnLockScreenToggle())); expectTrue(enableAppOnLockScreenToggle().checked); // Select the app that does not support lock screen again. appSelector.value = 'v1'; stylusPage.onSelectedAppChanged_(); - assertEquals('v1', browserProxy.setPreferredNoteTakingApp_); + assertEquals(2, browserProxy.setPreferredAppCount_); + assertEquals('v1', browserProxy.getPreferredNoteTakingAppId()); + return new Promise(function(resolve) { stylusPage.async(resolve); }); + }).then(function() { Polymer.dom.flush(); expectFalse(isVisible(enableAppOnLockScreenToggle())); + expectFalse(isVisible(enableAppOnLockScreenPolicyIndicator())); }); }); test('initial-app-lock-screen-enabled', function() { return new Promise(function(resolve) { - // No apps available. - browserProxy.onNoteTakingAppsUpdated_([ - entry('n1', 'v1', true, true) - ], false); + browserProxy.setNoteTakingApps([ + entry('n1', 'v1', true, LockScreenSupport.SUPPORTED) + ]); stylusPage.async(resolve); }).then(function() { Polymer.dom.flush(); assert(isVisible(enableAppOnLockScreenToggle())); expectFalse(enableAppOnLockScreenToggle().checked); + expectFalse(isVisible(enableAppOnLockScreenPolicyIndicator())); + + browserProxy.setNoteTakingApps([ + entry('n1', 'v1', true, LockScreenSupport.ENABLED) + ]); - devicePage.set( - 'prefs.settings.note_taking_app_enabled_on_lock_screen.value', - true); + return new Promise(function(resolve) { stylusPage.async(resolve); }); + }).then(function() { Polymer.dom.flush(); assert(isVisible(enableAppOnLockScreenToggle())); expectTrue(enableAppOnLockScreenToggle().checked); + expectFalse(isVisible(enableAppOnLockScreenPolicyIndicator())); - devicePage.set( - 'prefs.settings.note_taking_app_enabled_on_lock_screen.value', - false); + browserProxy.setNoteTakingApps([ + entry('n1', 'v1', true, LockScreenSupport.SUPPORTED) + ]); + + return new Promise(function(resolve) { stylusPage.async(resolve); }); + }).then(function() { Polymer.dom.flush(); assert(isVisible(enableAppOnLockScreenToggle())); expectFalse(enableAppOnLockScreenToggle().checked); + expectFalse(isVisible(enableAppOnLockScreenPolicyIndicator())); - browserProxy.onNoteTakingAppsUpdated_([], false); + browserProxy.setNoteTakingApps([]); return new Promise(function(resolve) { stylusPage.async(resolve); }); }).then(function() { Polymer.dom.flush(); @@ -1181,35 +1318,115 @@ cr.define('device_page_tests', function() { test('tap-on-enable-note-taking-on-lock-screen', function() { return new Promise(function(resolve) { - // No apps available. - browserProxy.onNoteTakingAppsUpdated_([ - entry('n1', 'v1', true, true) - ], false); + browserProxy.setNoteTakingApps([ + entry('n1', 'v1', true, LockScreenSupport.SUPPORTED) + ]); stylusPage.async(resolve); }).then(function() { Polymer.dom.flush(); assert(isVisible(enableAppOnLockScreenToggle())); expectFalse(enableAppOnLockScreenToggle().checked); + expectFalse(isVisible(enableAppOnLockScreenPolicyIndicator())); + + MockInteractions.tap(enableAppOnLockScreenToggle()); + assertEquals(1, browserProxy.setAppOnLockScreenCount_); + + return new Promise(function(resolve) { stylusPage.async(resolve); }); + }).then(function() { + Polymer.dom.flush(); + expectTrue(enableAppOnLockScreenToggle().checked); + expectFalse(isVisible(enableAppOnLockScreenPolicyIndicator())); + + expectEquals(LockScreenSupport.ENABLED, + browserProxy.getPreferredAppLockScreenState()); + + MockInteractions.tap(enableAppOnLockScreenToggle()); + assertEquals(2, browserProxy.setAppOnLockScreenCount_); + + return new Promise(function(resolve) { stylusPage.async(resolve); }); + }).then(function() { + Polymer.dom.flush(); + expectFalse(isVisible(enableAppOnLockScreenPolicyIndicator())); + expectFalse(enableAppOnLockScreenToggle().checked); + expectEquals(LockScreenSupport.SUPPORTED, + browserProxy.getPreferredAppLockScreenState()); + }); + }); - expectFalse( - devicePage.prefs.settings.note_taking_app_enabled_on_lock_screen - .value); + test('tap-on-enable-note-taking-on-lock-screen-label', function() { + return new Promise(function(resolve) { + browserProxy.setNoteTakingApps([ + entry('n1', 'v1', true, LockScreenSupport.SUPPORTED) + ]); + stylusPage.async(resolve); + }).then(function() { Polymer.dom.flush(); + assert(isVisible(enableAppOnLockScreenToggle())); expectFalse(enableAppOnLockScreenToggle().checked); - MockInteractions.tap(enableAppOnLockScreenToggle().$$('#control')); + MockInteractions.tap(enableAppOnLockScreenToggleLabel()); + assertEquals(1, browserProxy.setAppOnLockScreenCount_); + + return new Promise(function(resolve) { stylusPage.async(resolve); }); + }).then(function() { + Polymer.dom.flush(); expectTrue(enableAppOnLockScreenToggle().checked); - expectTrue( - devicePage.prefs.settings.note_taking_app_enabled_on_lock_screen - .value); + expectFalse(isVisible(enableAppOnLockScreenPolicyIndicator())); + + expectEquals(LockScreenSupport.ENABLED, + browserProxy.getPreferredAppLockScreenState()); + + MockInteractions.tap(enableAppOnLockScreenToggleLabel()); + assertEquals(2, browserProxy.setAppOnLockScreenCount_); + + return new Promise(function(resolve) { stylusPage.async(resolve); }); + }).then(function() { + Polymer.dom.flush(); + expectFalse(enableAppOnLockScreenToggle().checked); + expectEquals(LockScreenSupport.SUPPORTED, + browserProxy.getPreferredAppLockScreenState()); + }); + }); + + test('lock-screen-apps-disabled-by-policy', function() { + expectFalse(isVisible(enableAppOnLockScreenToggle())); + expectFalse(isVisible(enableAppOnLockScreenPolicyIndicator())); + + return new Promise(function(resolve) { + // Add an app with lock screen support. + browserProxy.addNoteTakingApp( + entry('n2', 'v2', true, LockScreenSupport.NOT_ALLOWED_BY_POLICY)); + stylusPage.async(resolve); + }).then(function() { + Polymer.dom.flush(); + assert(isVisible(enableAppOnLockScreenToggle())); + expectFalse(enableAppOnLockScreenToggle().checked); + expectTrue(isVisible(enableAppOnLockScreenPolicyIndicator())); + + // The toggle should be disabled, so enabling app on lock screen + // should not be attempted. + MockInteractions.tap(enableAppOnLockScreenToggle()); + assertEquals(0, browserProxy.setAppOnLockScreenCount_); + + return new Promise(function(resolve) { stylusPage.async(resolve); }); + }).then(function() { + Polymer.dom.flush(); - MockInteractions.tap(enableAppOnLockScreenToggle().$$('#control')); + // Tap on label should not work either. + MockInteractions.tap(enableAppOnLockScreenToggleLabel()); + assertEquals(0, browserProxy.setAppOnLockScreenCount_); + + return new Promise(function(resolve) { stylusPage.async(resolve); }); + }).then(function() { + Polymer.dom.flush(); + assert(isVisible(enableAppOnLockScreenToggle())); expectFalse(enableAppOnLockScreenToggle().checked); - expectFalse( - devicePage.prefs.settings.note_taking_app_enabled_on_lock_screen - .value); + expectTrue(isVisible(enableAppOnLockScreenPolicyIndicator())); + + expectEquals(LockScreenSupport.NOT_ALLOWED_BY_POLICY, + browserProxy.getPreferredAppLockScreenState()); }); }); });