chromium/chrome/browser/ash/lock_screen_apps/state_controller_unittest.cc

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/lock_screen_apps/state_controller.h"

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "ash/components/arc/session/arc_service_manager.h"
#include "ash/components/arc/session/arc_session.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/mojom/tray_action.mojom.h"
#include "ash/session/test_session_controller_client.h"
#include "base/base64.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/scoped_command_line.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/ash/arc/session/arc_session_manager.h"
#include "chrome/browser/ash/arc/test/test_arc_session_manager.h"
#include "chrome/browser/ash/lock_screen_apps/app_manager.h"
#include "chrome/browser/ash/lock_screen_apps/fake_lock_screen_profile_creator.h"
#include "chrome/browser/ash/lock_screen_apps/first_app_run_toast_manager.h"
#include "chrome/browser/ash/lock_screen_apps/focus_cycler_delegate.h"
#include "chrome/browser/ash/lock_screen_apps/state_observer.h"
#include "chrome/browser/ash/note_taking/note_taking_helper.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/test_extension_system.h"
#include "chrome/browser/ui/apps/chrome_app_delegate.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/ash/components/dbus/concierge/concierge_client.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "chromeos/dbus/power_manager/suspend.pb.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/web_contents_tester.h"
#include "extensions/browser/api/lock_screen_data/lock_screen_item_storage.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_contents.h"
#include "extensions/browser/app_window/native_app_window.h"
#include "extensions/common/api/app_runtime.h"
#include "extensions/common/extension_builder.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/window.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/events/devices/device_data_manager_test_api.h"

using ash::mojom::CloseLockScreenNoteReason;
using ash::mojom::LockScreenNoteOrigin;
using ash::mojom::TrayActionState;
using extensions::lock_screen_data::LockScreenItemStorage;
using lock_screen_apps::FakeLockScreenProfileCreator;

namespace {

// App IDs used for test apps.
const char kTestAppId[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
const char kSecondaryTestAppId[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";

// Key for pref containing lock screen data crypto key.
constexpr char kDataCryptoKeyPref[] = "lockScreenAppDataCryptoKey";

std::unique_ptr<arc::ArcSession> ArcSessionFactory() {
  ADD_FAILURE() << "Attempt to create arc session.";
  return nullptr;
}

scoped_refptr<const extensions::Extension> CreateTestNoteTakingApp(
    const std::string& app_id) {
  auto action_handlers =
      base::Value::List().Append(base::Value::Dict()
                                     .Set("action", "new_note")
                                     .Set("enabled_on_lock_screen", true));
  auto background = base::Value::Dict().Set(
      "scripts", base::Value::List().Append("background.js"));
  return extensions::ExtensionBuilder()
      .SetManifest(base::Value::Dict()
                       .Set("name", "Test App")
                       .Set("version", "1.0")
                       .Set("manifest_version", 2)
                       .Set("app", base::Value::Dict().Set(
                                       "background", std::move(background)))
                       .Set("action_handlers", std::move(action_handlers)))
      .SetID(app_id)
      .Build();
}

class TestFocusCyclerDelegate : public lock_screen_apps::FocusCyclerDelegate {
 public:
  TestFocusCyclerDelegate() = default;

  TestFocusCyclerDelegate(const TestFocusCyclerDelegate&) = delete;
  TestFocusCyclerDelegate& operator=(const TestFocusCyclerDelegate&) = delete;

  ~TestFocusCyclerDelegate() override = default;

  void RegisterLockScreenAppFocusHandler(
      const LockScreenAppFocusCallback& handler) override {
    focus_handler_ = handler;
    lock_screen_app_focused_ = true;
  }

  void UnregisterLockScreenAppFocusHandler() override {
    ASSERT_FALSE(focus_handler_.is_null());
    focus_handler_.Reset();
  }

  void HandleLockScreenAppFocusOut(bool reverse) override {
    ASSERT_FALSE(focus_handler_.is_null());
    lock_screen_app_focused_ = false;
  }

  void RequestAppFocus(bool reverse) {
    ASSERT_FALSE(focus_handler_.is_null());
    lock_screen_app_focused_ = true;
    focus_handler_.Run(reverse);
  }

  bool HasHandler() const { return !focus_handler_.is_null(); }

  bool lock_screen_app_focused() const { return lock_screen_app_focused_; }

 private:
  bool lock_screen_app_focused_ = false;
  LockScreenAppFocusCallback focus_handler_;
};

class TestAppManager : public lock_screen_apps::AppManager {
 public:
  enum class State {
    kNotInitialized,
    kStarted,
    kStopped,
  };

  TestAppManager(
      Profile* expected_primary_profile,
      lock_screen_apps::LockScreenProfileCreator* lock_screen_profile_creator)
      : expected_primary_profile_(expected_primary_profile),
        lock_screen_profile_creator_(lock_screen_profile_creator) {}

  TestAppManager(const TestAppManager&) = delete;
  TestAppManager& operator=(const TestAppManager&) = delete;

  ~TestAppManager() override = default;

  void Initialize(Profile* primary_profile,
                  lock_screen_apps::LockScreenProfileCreator*
                      lock_screen_profile_creator) override {
    ASSERT_EQ(State::kNotInitialized, state_);
    ASSERT_EQ(expected_primary_profile_, primary_profile);
    ASSERT_EQ(lock_screen_profile_creator_, lock_screen_profile_creator);

    state_ = State::kStopped;
  }

  void Start(const base::RepeatingClosure& change_callback) override {
    ASSERT_TRUE(change_callback_.is_null());
    ASSERT_FALSE(change_callback.is_null());
    change_callback_ = change_callback;
    state_ = State::kStarted;
  }

  void Stop() override {
    change_callback_.Reset();
    state_ = State::kStopped;
  }

  bool LaunchLockScreenApp() override {
    EXPECT_EQ(State::kStarted, state_);
    ++launch_count_;
    return app_launchable_;
  }

  bool IsLockScreenAppAvailable() const override {
    return state_ == State::kStarted && !app_id_.empty();
  }

  std::string GetLockScreenAppId() const override {
    if (state_ != State::kStarted)
      return std::string();
    return app_id_;
  }

  void SetInitialAppState(const std::string& app_id, bool app_launchable) {
    ASSERT_NE(State::kStarted, state_);

    app_launchable_ = app_launchable;
    app_id_ = app_id;
  }

  void UpdateApp(const std::string& app_id, bool app_launchable) {
    ASSERT_EQ(State::kStarted, state_);

    app_launchable_ = app_launchable;
    if (app_id == app_id_)
      return;
    app_id_ = app_id;

    change_callback_.Run();
  }

  State state() const { return state_; }

  int launch_count() const { return launch_count_; }
  void ResetLaunchCount() { launch_count_ = 0; }

 private:
  const raw_ptr<const Profile> expected_primary_profile_;
  raw_ptr<lock_screen_apps::LockScreenProfileCreator>
      lock_screen_profile_creator_;

  base::RepeatingClosure change_callback_;

  State state_ = State::kNotInitialized;

  // Number of requested app launches.
  int launch_count_ = 0;

  // Information about the test app:
  // The app ID.
  std::string app_id_;
  // Whether app launch should succeed.
  bool app_launchable_ = false;
};

class TestStateObserver : public lock_screen_apps::StateObserver {
 public:
  TestStateObserver() = default;

  TestStateObserver(const TestStateObserver&) = delete;
  TestStateObserver& operator=(const TestStateObserver&) = delete;

  ~TestStateObserver() override = default;

  void OnLockScreenNoteStateChanged(TrayActionState state) override {
    observed_states_.push_back(state);
  }

  const std::vector<TrayActionState>& observed_states() const {
    return observed_states_;
  }

  void ClearObservedStates() { observed_states_.clear(); }

 private:
  std::vector<TrayActionState> observed_states_;
};

class TestTrayAction : public ash::mojom::TrayAction {
 public:
  TestTrayAction() = default;

  TestTrayAction(const TestTrayAction&) = delete;
  TestTrayAction& operator=(const TestTrayAction&) = delete;

  ~TestTrayAction() override = default;

  mojo::PendingRemote<ash::mojom::TrayAction> CreateRemoteAndBind() {
    mojo::PendingRemote<ash::mojom::TrayAction> remote;
    receiver_.Bind(remote.InitWithNewPipeAndPassReceiver());
    return remote;
  }

  void SetClient(mojo::PendingRemote<ash::mojom::TrayActionClient> client,
                 TrayActionState state) override {
    client_.Bind(std::move(client));
    EXPECT_EQ(TrayActionState::kNotAvailable, state);
  }

  void UpdateLockScreenNoteState(TrayActionState state) override {
    observed_states_.push_back(state);
  }

  void SendNewNoteRequest(LockScreenNoteOrigin origin) {
    ASSERT_TRUE(client_);
    client_->RequestNewLockScreenNote(origin);
  }

  const std::vector<TrayActionState>& observed_states() const {
    return observed_states_;
  }

  void ClearObservedStates() { observed_states_.clear(); }

 private:
  mojo::Receiver<ash::mojom::TrayAction> receiver_{this};
  mojo::Remote<ash::mojom::TrayActionClient> client_;

  std::vector<TrayActionState> observed_states_;
};

// Wrapper around AppWindow used to manage the app window lifetime, and provide
// means to initialize/close the window,
class TestAppWindow : public content::WebContentsObserver {
 public:
  TestAppWindow(Profile* profile, extensions::AppWindow* window)
      : web_contents_(
            content::WebContentsTester::CreateTestWebContents(profile,
                                                              nullptr)),
        window_(window) {}

  TestAppWindow(const TestAppWindow&) = delete;
  TestAppWindow& operator=(const TestAppWindow&) = delete;

  ~TestAppWindow() override {
    // Make sure the window is initialized, so |window_| does not get leaked.
    if (!initialized_ && window_)
      Initialize(false /* shown */);

    Close();
  }

  void Initialize(bool shown) {
    ASSERT_FALSE(initialized_);
    ASSERT_TRUE(window_);
    initialized_ = true;

    extensions::AppWindow::CreateParams params;
    params.hidden = !shown;
    window_->Init(GURL(),
                  std::make_unique<extensions::AppWindowContentsImpl>(window_),
                  web_contents_->GetPrimaryMainFrame(), params);
    Observe(window_->web_contents());
  }

  void Close() {
    if (!window_)
      return;

    if (!initialized_)
      return;

    content::WebContentsDestroyedWatcher destroyed_watcher(
        window_->web_contents());
    window_->GetBaseWindow()->Close();
    destroyed_watcher.Wait();

    EXPECT_FALSE(window_);
    EXPECT_TRUE(closed_);
  }

  void WebContentsDestroyed() override {
    closed_ = true;
    window_ = nullptr;
  }

  extensions::AppWindow* window() { return window_; }

  bool closed() const { return closed_; }

 private:
  std::unique_ptr<content::WebContents> web_contents_;
  raw_ptr<extensions::AppWindow> window_;
  bool closed_ = false;
  bool initialized_ = false;
};

class LockScreenAppStateTest : public BrowserWithTestWindowTest {
 public:
  LockScreenAppStateTest() = default;
  LockScreenAppStateTest(const LockScreenAppStateTest&) = delete;
  LockScreenAppStateTest& operator=(const LockScreenAppStateTest&) = delete;
  ~LockScreenAppStateTest() override = default;

  void SetUp() override {
    ash::ConciergeClient::InitializeFake(/*fake_cicerone_client=*/nullptr);

    command_line_ = std::make_unique<base::test::ScopedCommandLine>();
    command_line_->GetProcessCommandLine()->InitFromArgv({""});
    SetUpCommandLine(command_line_->GetProcessCommandLine());

    BrowserWithTestWindowTest::SetUp();

    SetUpStylusAvailability();

    // SessionManager is created by
    // |AshTestHelper::bluetooth_config_test_helper()|.
    session_manager()->SetSessionState(
        session_manager::SessionState::LOGIN_PRIMARY);

    // Initialize arc session manager - NoteTakingHelper expects it to be set.
    arc_session_manager_ = arc::CreateTestArcSessionManager(
        std::make_unique<arc::ArcSessionRunner>(
            base::BindRepeating(&ArcSessionFactory)));

    ash::NoteTakingHelper::Initialize();

    InitExtensionSystem(profile());

    std::unique_ptr<FakeLockScreenProfileCreator> profile_creator =
        std::make_unique<FakeLockScreenProfileCreator>(profile_manager());
    lock_screen_profile_creator_ = profile_creator.get();

    std::unique_ptr<TestAppManager> app_manager =
        std::make_unique<TestAppManager>(profile(), profile_creator.get());
    app_manager_ = app_manager.get();

    focus_cycler_delegate_ = std::make_unique<TestFocusCyclerDelegate>();

    // Advance the clock to have non-null value.
    tick_clock_.Advance(base::Milliseconds(1));

    state_controller_ = std::make_unique<lock_screen_apps::StateController>();
    state_controller_->SetTrayActionForTesting(
        tray_action_.CreateRemoteAndBind());
    state_controller_->SetTickClockForTesting(&tick_clock_);
    state_controller_->SetLockScreenLockScreenProfileCreatorForTesting(
        std::move(profile_creator));
    state_controller_->SetAppManagerForTesting(std::move(app_manager));
    state_controller_->SetReadyCallbackForTesting(ready_waiter_.QuitClosure());
    state_controller_->Initialize();
    state_controller_->FlushTrayActionForTesting();
    state_controller_->SetFocusCyclerDelegate(focus_cycler_delegate_.get());

    state_controller_->AddObserver(&observer_);
  }

  void TearDown() override {
    // Add loop to wait for icon loading. Otherwise,
    // data_decoder::ServiceProvider is set as null, and data_decoder for icon
    // loading could cause crash.
    base::RunLoop().RunUntilIdle();
    state_controller_->RemoveObserver(&observer_);
    state_controller_->Shutdown();
    focus_cycler_delegate_.reset();
    app_manager_ = nullptr;
    lock_screen_profile_creator_ = nullptr;
    extensions::ExtensionSystem::Get(profile())->Shutdown();
    ash::NoteTakingHelper::Shutdown();
    arc_session_manager_.reset();
    app_window_.reset();
    BrowserWithTestWindowTest::TearDown();
    command_line_.reset();
    ash::ConciergeClient::Shutdown();
  }

  // Exposed so test fixtures can override default (empty) command line.
  virtual void SetUpCommandLine(base::CommandLine* command_line) {}

  // Sets up input device manager so stylus input is present.
  // Virtual so test fixture can override initial stylus availability.
  virtual void SetUpStylusAvailability() { SetStylusEnabled(); }

  // Adds a command line switch to enable stylus.
  void SetStylusEnabled() {
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
        ash::switches::kAshForceEnableStylusTools);
    ui::DeviceDataManagerTestApi()
        .NotifyObserversTouchscreenDeviceConfigurationChanged();
  }

  void InitExtensionSystem(Profile* profile) {
    extensions::TestExtensionSystem* extension_system =
        static_cast<extensions::TestExtensionSystem*>(
            extensions::ExtensionSystem::Get(profile));
    extension_system->CreateExtensionService(
        base::CommandLine::ForCurrentProcess(),
        base::FilePath() /* install_directory */,
        false /* autoupdate_enabled */);
  }

  void ExpectObservedStatesMatch(const std::vector<TrayActionState>& states,
                                 const std::string& message) {
    state_controller_->FlushTrayActionForTesting();
    EXPECT_EQ(states, observer()->observed_states()) << message;
    EXPECT_EQ(states, tray_action()->observed_states()) << message;
  }

  // Helper method to create and register an app window for lock screen note
  // taking action with the state controller.
  // Note that app window creation may fail if the app is not allowed to create
  // the app window for the action - in that case returned |TestAppWindow| will
  // have null |window| (rather than being null itself).
  std::unique_ptr<TestAppWindow> CreateNoteTakingWindow(
      Profile* profile,
      const extensions::Extension* extension) {
    return std::make_unique<TestAppWindow>(
        profile, state_controller()->CreateAppWindowForLockScreenAction(
                     profile, extension,
                     extensions::api::app_runtime::ActionType::kNewNote,
                     std::make_unique<ChromeAppDelegate>(profile, true)));
  }

  void ClearObservedStates() {
    state_controller_->FlushTrayActionForTesting();
    observer_.ClearObservedStates();
    tray_action_.ClearObservedStates();
  }

  void SetPrimaryProfileAndWaitUntilReady() {
    state_controller_->SetPrimaryProfile(profile());
    ready_waiter_.Run();
  }

  void SetFirstRunCompletedIfNeeded(const std::string& app_id) {
    if (is_first_app_run_test_)
      return;

    ScopedDictPrefUpdate dict_update(
        profile()->GetPrefs(), prefs::kNoteTakingAppsLockScreenToastShown);
    dict_update->Set(app_id, true);
  }

  // Helper method to move state controller to the specified state.
  // Should be called at the begining of tests, at most once.
  bool InitializeNoteTakingApp(TrayActionState target_state,
                               bool enable_app_launch) {
    app_manager_->SetInitialAppState(kTestAppId, enable_app_launch);

    SetPrimaryProfileAndWaitUntilReady();

    if (target_state == TrayActionState::kNotAvailable)
      return true;

    lock_screen_profile_creator_->CreateProfile();
    Profile* lock_screen_profile =
        lock_screen_profile_creator_->lock_screen_profile();

    app_ = CreateTestNoteTakingApp(kTestAppId);
    extensions::ExtensionSystem::Get(lock_screen_profile)
        ->extension_service()
        ->AddExtension(app_.get());
    SetFirstRunCompletedIfNeeded(app_->id());

    session_manager()->SetSessionState(session_manager::SessionState::LOCKED);
    state_controller_->FlushTrayActionForTesting();

    if (app_manager_->state() != TestAppManager::State::kStarted) {
      ADD_FAILURE() << "Lock app manager Start not invoked.";
      return false;
    }

    ClearObservedStates();

    if (state_controller_->GetLockScreenNoteState() !=
        TrayActionState::kAvailable) {
      ADD_FAILURE() << "Unable to move to available state.";
      return false;
    }
    if (target_state == TrayActionState::kAvailable)
      return true;

    tray_action()->SendNewNoteRequest(
        LockScreenNoteOrigin::kLockScreenButtonTap);
    state_controller_->FlushTrayActionForTesting();

    ClearObservedStates();

    if (state_controller_->GetLockScreenNoteState() !=
        TrayActionState::kLaunching) {
      ADD_FAILURE() << "Unable to move to launching state.";
      return false;
    }
    app_manager_->ResetLaunchCount();

    if (target_state == TrayActionState::kLaunching)
      return true;

    app_window_ = CreateNoteTakingWindow(lock_screen_profile, app());
    if (!app_window_->window()) {
      ADD_FAILURE() << "Not allowed to create app window.";
      return false;
    }

    app_window()->Initialize(true /* shown */);

    ClearObservedStates();

    return state_controller()->GetLockScreenNoteState() ==
           TrayActionState::kActive;
  }

  bool RelaunchLockScreenApp() {
    state_controller_->CloseLockScreenNote(
        CloseLockScreenNoteReason::kUnlockButtonPressed);
    tray_action_.SendNewNoteRequest(LockScreenNoteOrigin::kLockScreenButtonTap);
    state_controller_->FlushTrayActionForTesting();

    app_window_ = CreateNoteTakingWindow(LockScreenProfile(), app());
    app_window_->Initialize(true /* shown */);

    ClearObservedStates();

    return state_controller()->GetLockScreenNoteState() ==
           TrayActionState::kActive;
  }

  void CreateLockScreenProfile() {
    lock_screen_profile_creator_->CreateProfile();
  }

  Profile* LockScreenProfile() {
    return lock_screen_profile_creator_->lock_screen_profile();
  }

  session_manager::SessionManager* session_manager() {
    return session_manager::SessionManager::Get();
  }

  TestStateObserver* observer() { return &observer_; }

  TestTrayAction* tray_action() { return &tray_action_; }

  lock_screen_apps::StateController* state_controller() {
    return state_controller_.get();
  }

  TestAppManager* app_manager() { return app_manager_; }

  TestAppWindow* app_window() { return app_window_.get(); }
  const extensions::Extension* app() { return app_.get(); }

  TestFocusCyclerDelegate* focus_cycler_delegate() {
    return focus_cycler_delegate_.get();
  }

  FakeLockScreenProfileCreator* lock_screen_profile_creator() {
    return lock_screen_profile_creator_;
  }

  base::SimpleTestTickClock* tick_clock() { return &tick_clock_; }

 protected:
  // Should be set by tests that excercise the logic for the first lock screen
  // app run - i.e. logic for showing the first run toast dialog.
  // If not set, app will be marked as previously run (and toast dialog accepted
  // in |InitializeNoteTakingApp|)
  bool is_first_app_run_test_ = false;

  std::unique_ptr<base::test::ScopedCommandLine> command_line_;

  // Run loop used to throttle test until async state controller initialization
  // is fully complete. The quit closure for this run loop will be passed to
  // |state_controller_| as the callback to be run when the state controller is
  // ready for action.
  // NOTE: Tests should call |state_controller_->SetPrimaryProfile(Profile*)|
  // before running the loop, as that is the method that starts the state
  // controller.
  base::RunLoop ready_waiter_;

  // The StateController does not really have dependency on ARC, but this is
  // needed to properly initialize NoteTakingHelper.
  std::unique_ptr<arc::ArcServiceManager> arc_service_manager_;
  std::unique_ptr<arc::ArcSessionManager> arc_session_manager_;

  std::unique_ptr<lock_screen_apps::StateController> state_controller_;

  std::unique_ptr<TestFocusCyclerDelegate> focus_cycler_delegate_;

  TestStateObserver observer_;
  TestTrayAction tray_action_;
  raw_ptr<FakeLockScreenProfileCreator, DanglingUntriaged>
      lock_screen_profile_creator_ = nullptr;
  raw_ptr<TestAppManager, DanglingUntriaged> app_manager_ = nullptr;

  std::unique_ptr<TestAppWindow> app_window_;
  scoped_refptr<const extensions::Extension> app_;

  base::SimpleTestTickClock tick_clock_;

  data_decoder::test::InProcessDataDecoder in_process_data_decoder_;
};

class LockScreenAppStateKioskUserTest : public LockScreenAppStateTest {
 public:
  LockScreenAppStateKioskUserTest() {}

  LockScreenAppStateKioskUserTest(const LockScreenAppStateKioskUserTest&) =
      delete;
  LockScreenAppStateKioskUserTest& operator=(
      const LockScreenAppStateKioskUserTest&) = delete;

  ~LockScreenAppStateKioskUserTest() override {}

  // BrowserWithTestWindow:
  void LogIn(const std::string& email) override {
    const AccountId account_id = AccountId::FromUserEmail(email);
    // Log in as a kiosk user.
    user_manager()->AddKioskAppUser(account_id);
    user_manager()->UserLoggedIn(
        account_id,
        user_manager::FakeUserManager::GetFakeUsernameHash(account_id),
        /*browser_restart=*/false,
        /*is_child=*/false);
  }
};

// Tests that initially do not have stylus tools set as enabled.
class LockScreenAppStateNoStylusInputTest : public LockScreenAppStateTest {
 public:
  LockScreenAppStateNoStylusInputTest() = default;

  LockScreenAppStateNoStylusInputTest(
      const LockScreenAppStateNoStylusInputTest&) = delete;
  LockScreenAppStateNoStylusInputTest& operator=(
      const LockScreenAppStateNoStylusInputTest&) = delete;

  ~LockScreenAppStateNoStylusInputTest() override = default;

  void SetUpStylusAvailability() override {}
};

}  // namespace

TEST_F(LockScreenAppStateKioskUserTest, SetPrimaryProfile) {
  ASSERT_EQ(TestAppManager::State::kNotInitialized, app_manager()->state());
  SetPrimaryProfileAndWaitUntilReady();

  EXPECT_EQ(TestAppManager::State::kNotInitialized, app_manager()->state());
  EXPECT_EQ(TrayActionState::kNotAvailable,
            state_controller()->GetLockScreenNoteState());
  EXPECT_FALSE(lock_screen_profile_creator()->Initialized());
  ExpectObservedStatesMatch(std::vector<TrayActionState>(), "No state change.");
}

TEST_F(LockScreenAppStateNoStylusInputTest,
       StylusDetectedAfterInitializationAndScreenLock) {
  ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kNotAvailable, true));
  EXPECT_EQ(TestAppManager::State::kStopped, app_manager()->state());
  EXPECT_TRUE(LockScreenItemStorage::GetIfAllowed(profile()));

  session_manager()->SetSessionState(session_manager::SessionState::LOCKED);

  // Even though session was locked, test app manager is still stopped, and
  // lock screen apps are unavailable due to stylus not being detected.
  EXPECT_EQ(TestAppManager::State::kStopped, app_manager()->state());
  EXPECT_EQ(TrayActionState::kNotAvailable,
            state_controller()->GetLockScreenNoteState());
  ExpectObservedStatesMatch(std::vector<TrayActionState>(), "No state change.");

  // Enable stylus input.
  SetStylusEnabled();

  // Given that stylus was enabled, lock screen apps should be avaialble.
  EXPECT_EQ(TestAppManager::State::kStarted, app_manager()->state());
  EXPECT_EQ(TrayActionState::kAvailable,
            state_controller()->GetLockScreenNoteState());
  ExpectObservedStatesMatch({TrayActionState::kAvailable}, "Stylus enabled");
}

TEST_F(LockScreenAppStateNoStylusInputTest, StylusDetectedAfterInitialization) {
  ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kNotAvailable, true));
  EXPECT_EQ(TestAppManager::State::kStopped, app_manager()->state());

  // Enable stylus input after state controller initialization finishes, but
  // before screen lock.
  SetStylusEnabled();

  // Given that the session is still unlocked, lock screen apps are still
  // unavailable.
  EXPECT_EQ(TestAppManager::State::kStopped, app_manager()->state());
  EXPECT_EQ(TrayActionState::kNotAvailable,
            state_controller()->GetLockScreenNoteState());
  ExpectObservedStatesMatch(std::vector<TrayActionState>(), "No state change.");

  // Given that the screen is locked, lock screen apps should become available.
  session_manager()->SetSessionState(session_manager::SessionState::LOCKED);

  EXPECT_EQ(TrayActionState::kAvailable,
            state_controller()->GetLockScreenNoteState());
  EXPECT_EQ(TestAppManager::State::kStarted, app_manager()->state());
}

TEST_F(LockScreenAppStateTest, InitialState) {
  EXPECT_EQ(TrayActionState::kNotAvailable,
            state_controller()->GetLockScreenNoteState());

  EXPECT_EQ(TestAppManager::State::kNotInitialized, app_manager()->state());
  state_controller()->CloseLockScreenNote(
      CloseLockScreenNoteReason::kUnlockButtonPressed);

  EXPECT_EQ(TrayActionState::kNotAvailable,
            state_controller()->GetLockScreenNoteState());

  ExpectObservedStatesMatch(std::vector<TrayActionState>(), "No state change.");
}

TEST_F(LockScreenAppStateTest, SetPrimaryProfile) {
  EXPECT_EQ(TestAppManager::State::kNotInitialized, app_manager()->state());
  SetPrimaryProfileAndWaitUntilReady();

  EXPECT_EQ(TestAppManager::State::kStopped, app_manager()->state());
  EXPECT_EQ(TrayActionState::kNotAvailable,
            state_controller()->GetLockScreenNoteState());
  ExpectObservedStatesMatch(std::vector<TrayActionState>(), "No state change.");
}

TEST_F(LockScreenAppStateTest, SetPrimaryProfileWhenSessionLocked) {
  session_manager()->SetSessionState(session_manager::SessionState::LOCKED);
  EXPECT_EQ(TrayActionState::kNotAvailable,
            state_controller()->GetLockScreenNoteState());

  EXPECT_EQ(TestAppManager::State::kNotInitialized, app_manager()->state());

  app_manager()->SetInitialAppState(kTestAppId, true);
  SetPrimaryProfileAndWaitUntilReady();

  ASSERT_EQ(TestAppManager::State::kStarted, app_manager()->state());

  EXPECT_EQ(TrayActionState::kAvailable,
            state_controller()->GetLockScreenNoteState());

  ExpectObservedStatesMatch({TrayActionState::kAvailable}, "Available on lock");
}

TEST_F(LockScreenAppStateTest, InitLockScreenDataLockScreenItemStorage) {
  EXPECT_EQ(TestAppManager::State::kNotInitialized, app_manager()->state());
  SetPrimaryProfileAndWaitUntilReady();
  CreateLockScreenProfile();

  LockScreenItemStorage* lock_screen_item_storage =
      LockScreenItemStorage::GetIfAllowed(profile());
  ASSERT_TRUE(lock_screen_item_storage);

  std::string crypto_key_in_prefs =
      profile()->GetPrefs()->GetString(kDataCryptoKeyPref);
  ASSERT_FALSE(crypto_key_in_prefs.empty());
  ASSERT_TRUE(base::Base64Decode(crypto_key_in_prefs, &crypto_key_in_prefs));

  EXPECT_EQ(crypto_key_in_prefs,
            lock_screen_item_storage->crypto_key_for_testing());

  session_manager()->SetSessionState(session_manager::SessionState::LOCKED);

  EXPECT_FALSE(LockScreenItemStorage::GetIfAllowed(profile()));
  EXPECT_TRUE(LockScreenItemStorage::GetIfAllowed(LockScreenProfile()));
}

TEST_F(LockScreenAppStateTest,
       InitLockScreenDataLockScreenItemStorageWhileLocked) {
  EXPECT_EQ(TestAppManager::State::kNotInitialized, app_manager()->state());
  session_manager()->SetSessionState(session_manager::SessionState::LOCKED);
  SetPrimaryProfileAndWaitUntilReady();
  CreateLockScreenProfile();

  EXPECT_FALSE(LockScreenItemStorage::GetIfAllowed(profile()));

  LockScreenItemStorage* lock_screen_item_storage =
      LockScreenItemStorage::GetIfAllowed(LockScreenProfile());
  ASSERT_TRUE(lock_screen_item_storage);

  std::string crypto_key_in_prefs =
      profile()->GetPrefs()->GetString(kDataCryptoKeyPref);
  ASSERT_FALSE(crypto_key_in_prefs.empty());
  ASSERT_TRUE(base::Base64Decode(crypto_key_in_prefs, &crypto_key_in_prefs));

  EXPECT_EQ(crypto_key_in_prefs,
            lock_screen_item_storage->crypto_key_for_testing());

  session_manager()->SetSessionState(session_manager::SessionState::ACTIVE);

  EXPECT_TRUE(LockScreenItemStorage::GetIfAllowed(profile()));
  EXPECT_FALSE(LockScreenItemStorage::GetIfAllowed(LockScreenProfile()));
}

TEST_F(LockScreenAppStateTest,
       InitLockScreenDataLockScreenItemStorage_CryptoKeyExists) {
  std::string crypto_key_in_prefs = "0123456789ABCDEF0123456789ABCDEF";
  profile()->GetPrefs()->SetString(kDataCryptoKeyPref,
                                   base::Base64Encode(crypto_key_in_prefs));

  SetPrimaryProfileAndWaitUntilReady();
  CreateLockScreenProfile();

  LockScreenItemStorage* lock_screen_item_storage =
      LockScreenItemStorage::GetIfAllowed(profile());
  ASSERT_TRUE(lock_screen_item_storage);

  EXPECT_EQ(crypto_key_in_prefs,
            lock_screen_item_storage->crypto_key_for_testing());

  session_manager()->SetSessionState(session_manager::SessionState::LOCKED);

  EXPECT_FALSE(LockScreenItemStorage::GetIfAllowed(profile()));
  EXPECT_TRUE(LockScreenItemStorage::GetIfAllowed(LockScreenProfile()));
}

TEST_F(LockScreenAppStateTest, NoLockScreenProfile) {
  SetPrimaryProfileAndWaitUntilReady();

  LockScreenItemStorage* lock_screen_item_storage =
      LockScreenItemStorage::GetIfAllowed(profile());
  ASSERT_TRUE(lock_screen_item_storage);

  session_manager()->SetSessionState(session_manager::SessionState::LOCKED);

  EXPECT_FALSE(LockScreenItemStorage::GetIfAllowed(profile()));
  ASSERT_EQ(TestAppManager::State::kStarted, app_manager()->state());

  EXPECT_EQ(TrayActionState::kNotAvailable,
            state_controller()->GetLockScreenNoteState());

  EXPECT_TRUE(lock_screen_profile_creator()->Initialized());

  // Make sure that calling different methods does not crash, nor change state.
  tray_action()->SendNewNoteRequest(
      LockScreenNoteOrigin::kLockScreenButtonSwipe);
  state_controller()->CloseLockScreenNote(
      CloseLockScreenNoteReason::kUnlockButtonPressed);

  ExpectObservedStatesMatch(std::vector<TrayActionState>(),
                            "Expect no changes with no lock screen profile");
  EXPECT_EQ(TrayActionState::kNotAvailable,
            state_controller()->GetLockScreenNoteState());

  scoped_refptr<const extensions::Extension> app =
      CreateTestNoteTakingApp(kTestAppId);
  extensions::ExtensionSystem::Get(profile())
      ->extension_service()
      ->AddExtension(app.get());
  EXPECT_FALSE(state_controller()->CreateAppWindowForLockScreenAction(
      profile(), app.get(), extensions::api::app_runtime::ActionType::kNewNote,
      std::make_unique<ChromeAppDelegate>(profile(), true)));
}

TEST_F(LockScreenAppStateTest, SessionLock) {
  app_manager()->SetInitialAppState(kTestAppId, true);
  SetPrimaryProfileAndWaitUntilReady();
  ASSERT_EQ(TestAppManager::State::kStopped, app_manager()->state());

  session_manager()->SetSessionState(session_manager::SessionState::LOCKED);

  ASSERT_EQ(TestAppManager::State::kStarted, app_manager()->state());

  EXPECT_EQ(TrayActionState::kAvailable,
            state_controller()->GetLockScreenNoteState());

  ExpectObservedStatesMatch({TrayActionState::kAvailable}, "Available on lock");
  ClearObservedStates();

  // When the session is unlocked again, app manager is stopped, and tray action
  // disabled again.
  session_manager()->SetSessionState(session_manager::SessionState::ACTIVE);

  EXPECT_EQ(TestAppManager::State::kStopped, app_manager()->state());

  EXPECT_EQ(TrayActionState::kNotAvailable,
            state_controller()->GetLockScreenNoteState());

  ExpectObservedStatesMatch({TrayActionState::kNotAvailable},
                            "Not available on unlock");
  ClearObservedStates();

  // Test that subsequent session lock works as expected.
  session_manager()->SetSessionState(session_manager::SessionState::LOCKED);
  ASSERT_EQ(TestAppManager::State::kStarted, app_manager()->state());
  ExpectObservedStatesMatch({TrayActionState::kAvailable},
                            "Available on second lock");
}

TEST_F(LockScreenAppStateTest, SessionUnlockedWhileStartingAppManager) {
  SetPrimaryProfileAndWaitUntilReady();
  ASSERT_EQ(TestAppManager::State::kStopped, app_manager()->state());

  session_manager()->SetSessionState(session_manager::SessionState::LOCKED);

  ASSERT_EQ(TestAppManager::State::kStarted, app_manager()->state());

  session_manager()->SetSessionState(session_manager::SessionState::ACTIVE);
  ASSERT_EQ(TestAppManager::State::kStopped, app_manager()->state());

  EXPECT_EQ(TrayActionState::kNotAvailable,
            state_controller()->GetLockScreenNoteState());
  ExpectObservedStatesMatch(std::vector<TrayActionState>(),
                            "No state change on session unlock.");

  // Test that subsequent session lock works as expected.
  session_manager()->SetSessionState(session_manager::SessionState::LOCKED);

  ASSERT_EQ(TestAppManager::State::kStarted, app_manager()->state());
  app_manager()->UpdateApp(kTestAppId, true);

  EXPECT_EQ(TrayActionState::kAvailable,
            state_controller()->GetLockScreenNoteState());
  ExpectObservedStatesMatch({TrayActionState::kAvailable}, "Available on lock");
}

TEST_F(LockScreenAppStateTest, AppManagerNoApp) {
  SetPrimaryProfileAndWaitUntilReady();
  ASSERT_EQ(TestAppManager::State::kStopped, app_manager()->state());

  session_manager()->SetSessionState(session_manager::SessionState::LOCKED);

  EXPECT_EQ(TestAppManager::State::kStarted, app_manager()->state());

  EXPECT_EQ(TrayActionState::kNotAvailable,
            state_controller()->GetLockScreenNoteState());
  ExpectObservedStatesMatch(std::vector<TrayActionState>(),
                            "No state change on session lock.");

  tray_action()->SendNewNoteRequest(
      LockScreenNoteOrigin::kLockScreenButtonSwipe);
  state_controller()->FlushTrayActionForTesting();

  EXPECT_EQ(TrayActionState::kNotAvailable,
            state_controller()->GetLockScreenNoteState());
  ExpectObservedStatesMatch(std::vector<TrayActionState>(),
                            "No state change on note request.");

  // App manager should be started on next session lock.
  session_manager()->SetSessionState(session_manager::SessionState::ACTIVE);
  ASSERT_EQ(TestAppManager::State::kStopped, app_manager()->state());
  app_manager()->SetInitialAppState(kTestAppId, false);

  session_manager()->SetSessionState(session_manager::SessionState::LOCKED);

  EXPECT_EQ(TrayActionState::kAvailable,
            state_controller()->GetLockScreenNoteState());
  ExpectObservedStatesMatch({TrayActionState::kAvailable}, "Available on lock");
}

TEST_F(LockScreenAppStateTest, AppAvailabilityChanges) {
  SetPrimaryProfileAndWaitUntilReady();
  ASSERT_EQ(TestAppManager::State::kStopped, app_manager()->state());

  app_manager()->SetInitialAppState(kTestAppId, false);
  session_manager()->SetSessionState(session_manager::SessionState::LOCKED);

  EXPECT_EQ(TestAppManager::State::kStarted, app_manager()->state());

  EXPECT_EQ(TrayActionState::kAvailable,
            state_controller()->GetLockScreenNoteState());
  ExpectObservedStatesMatch({TrayActionState::kAvailable}, "Available on lock");
  ClearObservedStates();

  app_manager()->UpdateApp("", false);

  EXPECT_EQ(TrayActionState::kNotAvailable,
            state_controller()->GetLockScreenNoteState());
  ExpectObservedStatesMatch({TrayActionState::kNotAvailable},
                            "Not available on app cleared");
  ClearObservedStates();

  app_manager()->UpdateApp(kSecondaryTestAppId, true);

  EXPECT_EQ(TrayActionState::kAvailable,
            state_controller()->GetLockScreenNoteState());
  ExpectObservedStatesMatch({TrayActionState::kAvailable},
                            "Available on other app set");
}

TEST_F(LockScreenAppStateTest, CloseAppWhileLaunching) {
  ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kLaunching,
                                      true /* enable_app_launch */));

  state_controller()->CloseLockScreenNote(
      CloseLockScreenNoteReason::kUnlockButtonPressed);
  state_controller()->FlushTrayActionForTesting();

  EXPECT_EQ(TrayActionState::kAvailable,
            state_controller()->GetLockScreenNoteState());

  EXPECT_FALSE(state_controller()->CreateAppWindowForLockScreenAction(
      profile(), app(), extensions::api::app_runtime::ActionType::kNewNote,
      std::make_unique<ChromeAppDelegate>(profile(), true)));

  ExpectObservedStatesMatch({TrayActionState::kAvailable},
                            "Close app window cancels launch.");
}

TEST_F(LockScreenAppStateTest, HandleActionWhenNotAvaiable) {
  ASSERT_EQ(TrayActionState::kNotAvailable,
            state_controller()->GetLockScreenNoteState());

  tray_action()->SendNewNoteRequest(
      LockScreenNoteOrigin::kLockScreenButtonSwipe);
  state_controller()->FlushTrayActionForTesting();

  ExpectObservedStatesMatch(std::vector<TrayActionState>(),
                            "No state change on note request");
}

TEST_F(LockScreenAppStateTest, HandleAction) {
  ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kAvailable,
                                      true /* enable_app_launch */));

  tray_action()->SendNewNoteRequest(
      LockScreenNoteOrigin::kLockScreenButtonSwipe);
  state_controller()->FlushTrayActionForTesting();

  ExpectObservedStatesMatch({TrayActionState::kLaunching},
                            "Launch on new note request");
  ClearObservedStates();
  EXPECT_EQ(1, app_manager()->launch_count());

  tray_action()->SendNewNoteRequest(
      LockScreenNoteOrigin::kLockScreenButtonSwipe);
  state_controller()->FlushTrayActionForTesting();

  // There should be no state change - the state_controller was already in
  // launching state when the request was received.
  ExpectObservedStatesMatch(std::vector<TrayActionState>(),
                            "No state change on repeated launch");
  EXPECT_EQ(1, app_manager()->launch_count());
}

TEST_F(LockScreenAppStateTest, HandleActionWithLaunchFailure) {
  ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kAvailable,
                                      false /* enable_app_launch */));

  tray_action()->SendNewNoteRequest(
      LockScreenNoteOrigin::kLockScreenButtonSwipe);
  state_controller()->FlushTrayActionForTesting();

  EXPECT_EQ(TrayActionState::kAvailable,
            state_controller()->GetLockScreenNoteState());
  ExpectObservedStatesMatch(
      {TrayActionState::kLaunching, TrayActionState::kAvailable},
      "Failed launch on new note request");
  ClearObservedStates();

  EXPECT_EQ(1, app_manager()->launch_count());

  tray_action()->SendNewNoteRequest(
      LockScreenNoteOrigin::kLockScreenButtonSwipe);
  state_controller()->FlushTrayActionForTesting();

  EXPECT_EQ(TrayActionState::kAvailable,
            state_controller()->GetLockScreenNoteState());

  ExpectObservedStatesMatch(
      {TrayActionState::kLaunching, TrayActionState::kAvailable},
      "Second failed launch on new note request");
  EXPECT_EQ(2, app_manager()->launch_count());
}

TEST_F(LockScreenAppStateTest, AppWindowRegistration) {
  ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kAvailable,
                                      true /* enable_app_launch */));

  std::unique_ptr<TestAppWindow> app_window =
      CreateNoteTakingWindow(LockScreenProfile(), app());
  EXPECT_FALSE(app_window->window());

  tray_action()->SendNewNoteRequest(
      LockScreenNoteOrigin::kLockScreenButtonSwipe);
  state_controller()->FlushTrayActionForTesting();

  EXPECT_EQ(TrayActionState::kLaunching,
            state_controller()->GetLockScreenNoteState());
  observer()->ClearObservedStates();
  tray_action()->ClearObservedStates();

  std::unique_ptr<TestAppWindow> non_eligible_app_window =
      CreateNoteTakingWindow(profile(), app());
  EXPECT_FALSE(non_eligible_app_window->window());

  EXPECT_FALSE(state_controller()->CreateAppWindowForLockScreenAction(
      LockScreenProfile(), app(),
      extensions::api::app_runtime::ActionType::kNone,
      std::make_unique<ChromeAppDelegate>(LockScreenProfile(), true)));

  app_window = CreateNoteTakingWindow(LockScreenProfile(), app());
  ASSERT_TRUE(app_window->window());

  app_window->Initialize(true /* shown */);
  EXPECT_EQ(TrayActionState::kActive,
            state_controller()->GetLockScreenNoteState());

  // Test that second app window cannot be registered.
  std::unique_ptr<TestAppWindow> second_app_window =
      CreateNoteTakingWindow(LockScreenProfile(), app());
  EXPECT_FALSE(second_app_window->window());

  // Test the app window does not get closed by itself.
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(app_window->closed());

  EXPECT_EQ(TrayActionState::kActive,
            state_controller()->GetLockScreenNoteState());

  // Closing the second app window, will not change the state.
  second_app_window->Close();
  EXPECT_EQ(TrayActionState::kActive,
            state_controller()->GetLockScreenNoteState());

  app_window->Close();
  EXPECT_EQ(TrayActionState::kAvailable,
            state_controller()->GetLockScreenNoteState());
}

TEST_F(LockScreenAppStateTest, AppWindowClosedBeforeBeingShown) {
  ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kLaunching,
                                      true /* enable_app_launch */));

  std::unique_ptr<TestAppWindow> app_window =
      CreateNoteTakingWindow(LockScreenProfile(), app());
  ASSERT_TRUE(app_window->window());
  app_window->Initialize(false /* shown */);

  app_window->Close();
  EXPECT_EQ(TrayActionState::kAvailable,
            state_controller()->GetLockScreenNoteState());
}

TEST_F(LockScreenAppStateTest, AppWindowClosedOnSessionUnlock) {
  ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kActive,
                                      true /* enable_app_launch */));

  session_manager()->SetSessionState(session_manager::SessionState::ACTIVE);
  EXPECT_EQ(TrayActionState::kNotAvailable,
            state_controller()->GetLockScreenNoteState());

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(app_window()->closed());
}

TEST_F(LockScreenAppStateTest, CloseAppWindowOnSuspend) {
  ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kActive,
                                      true /* enable_app_launch */));

  chromeos::FakePowerManagerClient::Get()->SendSuspendImminent(
      power_manager::SuspendImminent_Reason_OTHER);
  EXPECT_EQ(TrayActionState::kAvailable,
            state_controller()->GetLockScreenNoteState());

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(app_window()->closed());
}

TEST_F(LockScreenAppStateTest, AppWindowClosedOnAppUnload) {
  ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kActive,
                                      true /* enable_app_launch */));

  extensions::ExtensionSystem::Get(LockScreenProfile())
      ->extension_service()
      ->UnloadExtension(app()->id(),
                        extensions::UnloadedExtensionReason::UNINSTALL);
  app_manager()->UpdateApp("", false);

  EXPECT_EQ(TrayActionState::kNotAvailable,
            state_controller()->GetLockScreenNoteState());

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(app_window()->closed());
}

TEST_F(LockScreenAppStateTest, AppWindowClosedOnNoteTakingAppChange) {
  ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kActive,
                                      true /* enable_app_launch */));

  scoped_refptr<const extensions::Extension> secondary_app =
      CreateTestNoteTakingApp(kSecondaryTestAppId);
  extensions::ExtensionSystem::Get(LockScreenProfile())
      ->extension_service()
      ->AddExtension(secondary_app.get());
  SetFirstRunCompletedIfNeeded(secondary_app->id());

  app_manager()->UpdateApp(secondary_app->id(), true);

  EXPECT_EQ(TrayActionState::kAvailable,
            state_controller()->GetLockScreenNoteState());

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(app_window()->closed());

  tray_action()->SendNewNoteRequest(
      LockScreenNoteOrigin::kLockScreenButtonSwipe);
  state_controller()->FlushTrayActionForTesting();

  std::unique_ptr<TestAppWindow> app_window =
      CreateNoteTakingWindow(LockScreenProfile(), app());
  EXPECT_FALSE(app_window->window());
  ASSERT_EQ(TrayActionState::kLaunching,
            state_controller()->GetLockScreenNoteState());

  std::unique_ptr<TestAppWindow> secondary_app_window =
      CreateNoteTakingWindow(LockScreenProfile(), secondary_app.get());
  ASSERT_TRUE(secondary_app_window->window());

  secondary_app_window->Initialize(true /* shown*/);
  EXPECT_EQ(TrayActionState::kActive,
            state_controller()->GetLockScreenNoteState());

  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(secondary_app_window->closed());

  // Uninstall the app and test the secondary app window is closed.
  extensions::ExtensionSystem::Get(LockScreenProfile())
      ->extension_service()
      ->UnloadExtension(secondary_app->id(),
                        extensions::UnloadedExtensionReason::UNINSTALL);
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(secondary_app_window->closed());
}

// Goes through different states with no focus cycler set; mainly to check
// there are no crashes.
TEST_F(LockScreenAppStateTest, NoFocusCyclerDelegate) {
  lock_screen_apps::StateController::Get()->SetFocusCyclerDelegate(nullptr);

  ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kActive,
                                      true /* enable_app_launch */));

  state_controller()->CloseLockScreenNote(
      CloseLockScreenNoteReason::kUnlockButtonPressed);
  state_controller()->FlushTrayActionForTesting();

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(app_window()->closed());
}

TEST_F(LockScreenAppStateTest, ResetFocusCyclerDelegateWhileActive) {
  ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kActive,
                                      true /* enable_app_launch */));

  lock_screen_apps::StateController::Get()->SetFocusCyclerDelegate(nullptr);
  ASSERT_FALSE(focus_cycler_delegate()->HasHandler());

  lock_screen_apps::StateController::Get()->SetFocusCyclerDelegate(
      focus_cycler_delegate());
  EXPECT_TRUE(focus_cycler_delegate()->HasHandler());
}

TEST_F(LockScreenAppStateTest, FocusCyclerDelegateGetsSetOnAppWindowCreation) {
  ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kAvailable,
                                      true /* enable_app_launch */));

  tray_action()->SendNewNoteRequest(
      LockScreenNoteOrigin::kLockScreenButtonSwipe);
  state_controller()->FlushTrayActionForTesting();

  EXPECT_FALSE(focus_cycler_delegate()->HasHandler());

  std::unique_ptr<TestAppWindow> app_window =
      CreateNoteTakingWindow(LockScreenProfile(), app());
  app_window->Initialize(true /* shown */);

  EXPECT_TRUE(focus_cycler_delegate()->HasHandler());

  state_controller()->CloseLockScreenNote(
      CloseLockScreenNoteReason::kUnlockButtonPressed);

  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(focus_cycler_delegate()->HasHandler());
  EXPECT_TRUE(app_window->closed());
}

TEST_F(LockScreenAppStateTest, TakeFocus) {
  ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kActive,
                                      true /* enable_app_launch */));

  auto regular_app_window = std::make_unique<TestAppWindow>(
      profile(),
      new extensions::AppWindow(
          profile(), std::make_unique<ChromeAppDelegate>(profile(), true),
          app()));
  EXPECT_FALSE(state_controller()->HandleTakeFocus(
      regular_app_window->window()->web_contents(), true));
  EXPECT_TRUE(focus_cycler_delegate()->lock_screen_app_focused());

  ASSERT_TRUE(state_controller()->HandleTakeFocus(
      app_window()->window()->web_contents(), true));
  EXPECT_FALSE(focus_cycler_delegate()->lock_screen_app_focused());

  focus_cycler_delegate()->RequestAppFocus(true);
  EXPECT_TRUE(focus_cycler_delegate()->lock_screen_app_focused());
}

TEST_F(LockScreenAppStateTest, CloseNoteInActiveState) {
  ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kActive,
                                      true /* enable_app_launch */));

  state_controller()->CloseLockScreenNote(
      CloseLockScreenNoteReason::kUnlockButtonPressed);
  state_controller()->FlushTrayActionForTesting();

  EXPECT_EQ(TrayActionState::kAvailable,
            state_controller()->GetLockScreenNoteState());

  ExpectObservedStatesMatch({TrayActionState::kAvailable},
                            "Close lock screen note.");
  ClearObservedStates();

  EXPECT_TRUE(app_window()->closed());
}

TEST_F(LockScreenAppStateTest, CloseNoteWhileLaunching) {
  ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kLaunching,
                                      true /* enable_app_launch */));

  state_controller()->CloseLockScreenNote(
      CloseLockScreenNoteReason::kUnlockButtonPressed);
  state_controller()->FlushTrayActionForTesting();

  EXPECT_EQ(TrayActionState::kAvailable,
            state_controller()->GetLockScreenNoteState());

  EXPECT_FALSE(state_controller()->CreateAppWindowForLockScreenAction(
      profile(), app(), extensions::api::app_runtime::ActionType::kNewNote,
      std::make_unique<ChromeAppDelegate>(profile(), true)));

  ExpectObservedStatesMatch({TrayActionState::kAvailable},
                            "Close lock screen note.");
}

TEST_F(LockScreenAppStateTest, ToastDialogShownOnFirstAppRun) {
  is_first_app_run_test_ = true;

  ASSERT_TRUE(InitializeNoteTakingApp(TrayActionState::kActive,
                                      true /* enable_app_launch */));
  // Make sure that the app window is activated, because the toast dialog is
  // shown only after lock screen app window activation.
  app_window()->window()->OnNativeWindowActivated();
  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE(state_controller()->first_app_run_toast_manager()->widget());
  EXPECT_TRUE(
      state_controller()->first_app_run_toast_manager()->widget()->IsVisible());

  // The toast should be shown again after app re-launch, as the toast widget
  // was not dismissed by the user.
  ASSERT_TRUE(RelaunchLockScreenApp());
  // Make sure that the app window is activated, because the toast dialog is
  // shown only after lock screen app window activation.
  app_window()->window()->OnNativeWindowActivated();
  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE(state_controller()->first_app_run_toast_manager()->widget());
  EXPECT_TRUE(
      state_controller()->first_app_run_toast_manager()->widget()->IsVisible());

  state_controller()->first_app_run_toast_manager()->widget()->Close();
  base::RunLoop().RunUntilIdle();

  // Relaunch the note taking app - this time the toast bubble should not have
  // been shown.
  ASSERT_TRUE(RelaunchLockScreenApp());
  // Make sure that the app window is activated, because the toast dialog is
  // shown only after lock screen app window activation.
  app_window()->window()->OnNativeWindowActivated();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(state_controller()->first_app_run_toast_manager()->widget());
}