blob: 8b0ef3b949f929f1697efa2a6d47bd0a643be837 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_impl.h"
#include <stddef.h>
#include <algorithm>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "ash/display/display_configuration_controller.h"
#include "ash/display/screen_orientation_controller_chromeos.h"
#include "ash/public/cpp/app_launch_id.h"
#include "ash/public/cpp/shelf_item.h"
#include "ash/public/cpp/shelf_item_delegate.h"
#include "ash/shelf/shelf_application_menu_model.h"
#include "ash/shelf/shelf_constants.h"
#include "ash/shelf/shelf_controller.h"
#include "ash/shelf/shelf_model.h"
#include "ash/shelf/shelf_model_observer.h"
#include "ash/shell.h"
#include "ash/test/ash_test_helper.h"
#include "ash/test/shell_test_api.h"
#include "ash/test/test_shell_delegate.h"
#include "ash/wm/maximize_mode/maximize_mode_controller.h"
#include "ash/wm/window_util.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/files/file_path.h"
#include "base/json/json_string_value_serializer.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/chromeos/arc/arc_support_host.h"
#include "chrome/browser/chromeos/arc/arc_util.h"
#include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/chromeos/login/users/scoped_user_manager_enabler.h"
#include "chrome/browser/chromeos/login/users/wallpaper/wallpaper_manager.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/test_extension_system.h"
#include "chrome/browser/lifetime/scoped_keep_alive.h"
#include "chrome/browser/prefs/browser_prefs.h"
#include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
#include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
#include "chrome/browser/ui/app_list/arc/arc_app_test.h"
#include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
#include "chrome/browser/ui/app_list/arc/arc_default_app_list.h"
#include "chrome/browser/ui/apps/chrome_app_delegate.h"
#include "chrome/browser/ui/ash/chrome_launcher_prefs.h"
#include "chrome/browser/ui/ash/launcher/app_window_launcher_controller.h"
#include "chrome/browser/ui/ash/launcher/arc_app_deferred_launcher_controller.h"
#include "chrome/browser/ui/ash/launcher/browser_status_monitor.h"
#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_util.h"
#include "chrome/browser/ui/ash/launcher/extension_app_window_launcher_item_controller.h"
#include "chrome/browser/ui/ash/launcher/launcher_controller_helper.h"
#include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
#include "chrome/browser/ui/ash/multi_user/multi_user_window_manager.h"
#include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_chromeos.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chrome/test/base/test_browser_window_aura.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/chromeos_switches.h"
#include "components/arc/arc_util.h"
#include "components/arc/common/app.mojom.h"
#include "components/arc/test/fake_app_instance.h"
#include "components/exo/shell_surface.h"
#include "components/prefs/pref_notifier_impl.h"
#include "components/signin/core/account_id/account_id.h"
#include "components/sync/model/attachments/attachment_service_proxy_for_test.h"
#include "components/sync/model/fake_sync_change_processor.h"
#include "components/sync/model/sync_error_factory_mock.h"
#include "components/sync/protocol/sync.pb.h"
#include "components/sync_preferences/pref_model_associator.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/user_manager/fake_user_manager.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/web_contents_tester.h"
#include "extensions/browser/app_window/app_window_contents.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/browser/app_window/native_app_window.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_constants.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/client/window_parenting_client.h"
#include "ui/aura/window.h"
#include "ui/base/models/menu_model.h"
#include "ui/display/display.h"
#include "ui/display/display_switches.h"
#include "ui/display/screen.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event_constants.h"
#include "ui/views/widget/widget.h"
using base::ASCIIToUTF16;
using extensions::Extension;
using extensions::Manifest;
using extensions::UnloadedExtensionInfo;
using arc::mojom::OrientationLock;
namespace {
const char* offline_gmail_url = "https://mail.google.com/mail/mu/u";
const char* gmail_url = "https://mail.google.com/mail/u";
const char* kGmailLaunchURL = "https://mail.google.com/mail/ca";
// An extension prefix.
const char kCrxAppPrefix[] = "_crx_";
// Dummy app id is used to put at least one pin record to prevent initializing
// pin model with default apps that can affect some tests.
const char kDummyAppId[] = "dummyappid_dummyappid_dummyappid";
// ShelfModelObserver implementation that tracks what messages are invoked.
class TestShelfModelObserver : public ash::ShelfModelObserver {
public:
TestShelfModelObserver() {}
~TestShelfModelObserver() override {}
// Overridden from ash::ShelfModelObserver:
void ShelfItemAdded(int index) override {
++added_;
last_index_ = index;
}
void ShelfItemRemoved(int index, const ash::ShelfItem& old_item) override {
++removed_;
last_index_ = index;
}
void ShelfItemChanged(int index, const ash::ShelfItem& old_item) override {
++changed_;
last_index_ = index;
}
void ShelfItemMoved(int start_index, int target_index) override {
last_index_ = target_index;
}
void clear_counts() {
added_ = 0;
removed_ = 0;
changed_ = 0;
last_index_ = 0;
}
int added() const { return added_; }
int removed() const { return removed_; }
int changed() const { return changed_; }
int last_index() const { return last_index_; }
private:
int added_ = 0;
int removed_ = 0;
int changed_ = 0;
int last_index_ = 0;
DISALLOW_COPY_AND_ASSIGN(TestShelfModelObserver);
};
// Test implementation of AppIconLoader.
class TestAppIconLoaderImpl : public AppIconLoader {
public:
TestAppIconLoaderImpl() = default;
~TestAppIconLoaderImpl() override = default;
void AddSupportedApp(const std::string& id) { supported_apps_.insert(id); }
// AppIconLoader implementation:
bool CanLoadImageForApp(const std::string& id) override {
return supported_apps_.find(id) != supported_apps_.end();
}
void FetchImage(const std::string& id) override { ++fetch_count_; }
void ClearImage(const std::string& id) override { ++clear_count_; }
void UpdateImage(const std::string& id) override {}
int fetch_count() const { return fetch_count_; }
int clear_count() const { return clear_count_; }
private:
int fetch_count_ = 0;
int clear_count_ = 0;
std::set<std::string> supported_apps_;
DISALLOW_COPY_AND_ASSIGN(TestAppIconLoaderImpl);
};
// Test implementation of LauncherControllerHelper.
class TestLauncherControllerHelper : public LauncherControllerHelper {
public:
TestLauncherControllerHelper() : LauncherControllerHelper(nullptr) {}
explicit TestLauncherControllerHelper(Profile* profile)
: LauncherControllerHelper(profile) {}
~TestLauncherControllerHelper() override {}
// Sets the id for the specified tab.
void SetAppID(content::WebContents* tab, const std::string& id) {
tab_id_map_[tab] = id;
}
// Returns true if there is an id registered for |tab|.
bool HasAppID(content::WebContents* tab) const {
return tab_id_map_.find(tab) != tab_id_map_.end();
}
// LauncherControllerHelper:
std::string GetAppID(content::WebContents* tab) override {
return tab_id_map_.find(tab) != tab_id_map_.end() ? tab_id_map_[tab] :
std::string();
}
bool IsValidIDForCurrentUser(const std::string& id) const override {
for (TabToStringMap::const_iterator i = tab_id_map_.begin();
i != tab_id_map_.end(); ++i) {
if (i->second == id)
return true;
}
return false;
}
ArcAppListPrefs* GetArcAppListPrefs() const override { return nullptr; }
private:
typedef std::map<content::WebContents*, std::string> TabToStringMap;
TabToStringMap tab_id_map_;
DISALLOW_COPY_AND_ASSIGN(TestLauncherControllerHelper);
};
// Test implementation of a V2 app launcher item controller.
class TestV2AppLauncherItemController : public ash::ShelfItemDelegate {
public:
explicit TestV2AppLauncherItemController(const std::string& app_id)
: ash::ShelfItemDelegate(ash::AppLaunchId(app_id)) {}
~TestV2AppLauncherItemController() override {}
// Override for ash::ShelfItemDelegate:
void ItemSelected(std::unique_ptr<ui::Event> event,
int64_t display_id,
ash::ShelfLaunchSource source,
const ItemSelectedCallback& callback) override {
callback.Run(ash::SHELF_ACTION_WINDOW_ACTIVATED, base::nullopt);
}
void ExecuteCommand(uint32_t command_id, int32_t event_flags) override {}
void Close() override {}
private:
DISALLOW_COPY_AND_ASSIGN(TestV2AppLauncherItemController);
};
// A callback that does nothing after shelf item selection handling.
void NoopCallback(ash::ShelfAction action, base::Optional<ash::MenuItemList>) {}
// Simulates selection of the shelf item.
void SelectItem(ash::ShelfItemDelegate* delegate) {
std::unique_ptr<ui::Event> event = base::MakeUnique<ui::MouseEvent>(
ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(), ui::EventTimeForNow(),
ui::EF_NONE, 0);
delegate->ItemSelected(std::move(event), display::kInvalidDisplayId,
ash::LAUNCH_FROM_UNKNOWN, base::Bind(&NoopCallback));
}
} // namespace
class ChromeLauncherControllerImplTest : public BrowserWithTestWindowTest {
protected:
ChromeLauncherControllerImplTest()
: BrowserWithTestWindowTest(Browser::TYPE_TABBED, false) {}
~ChromeLauncherControllerImplTest() override {}
void SetUp() override {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
command_line->AppendSwitch(switches::kUseFirstDisplayAsInternal);
app_list::AppListSyncableServiceFactory::SetUseInTesting();
BrowserWithTestWindowTest::SetUp();
if (!profile_manager_) {
profile_manager_.reset(
new TestingProfileManager(TestingBrowserProcess::GetGlobal()));
ASSERT_TRUE(profile_manager_->SetUp());
}
model_ = ash::Shell::Get()->shelf_controller()->model();
model_observer_.reset(new TestShelfModelObserver);
model_->AddObserver(model_observer_.get());
base::DictionaryValue manifest;
manifest.SetString(extensions::manifest_keys::kName,
"launcher controller test extension");
manifest.SetString(extensions::manifest_keys::kVersion, "1");
manifest.SetString(extensions::manifest_keys::kDescription,
"for testing pinned apps");
base::DictionaryValue manifest_platform_app;
manifest_platform_app.SetString(extensions::manifest_keys::kName,
"launcher controller test platform app");
manifest_platform_app.SetString(extensions::manifest_keys::kVersion, "1");
manifest_platform_app.SetString(extensions::manifest_keys::kDescription,
"for testing pinned platform apps");
manifest_platform_app.SetString(extensions::manifest_keys::kApp, "true");
manifest_platform_app.Set(extensions::manifest_keys::kPlatformAppBackground,
new base::DictionaryValue());
base::ListValue* scripts = new base::ListValue();
scripts->AppendString("main.js");
manifest_platform_app.Set(
extensions::manifest_keys::kPlatformAppBackgroundScripts, scripts);
extensions::TestExtensionSystem* extension_system(
static_cast<extensions::TestExtensionSystem*>(
extensions::ExtensionSystem::Get(profile())));
extension_service_ = extension_system->CreateExtensionService(
base::CommandLine::ForCurrentProcess(), base::FilePath(), false);
extension_service_->Init();
if (auto_start_arc_test_)
arc_test_.SetUp(profile());
// Wait until |extension_system| is signaled as started.
base::RunLoop run_loop;
extension_system->ready().Post(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
app_service_ =
app_list::AppListSyncableServiceFactory::GetForProfile(profile());
StartAppSyncService(syncer::SyncDataList());
std::string error;
extension_chrome_ = Extension::Create(base::FilePath(), Manifest::UNPACKED,
manifest, Extension::NO_FLAGS,
extension_misc::kChromeAppId, &error);
extension1_ = Extension::Create(base::FilePath(), Manifest::UNPACKED,
manifest, Extension::NO_FLAGS,
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", &error);
extension2_ = Extension::Create(base::FilePath(), Manifest::UNPACKED,
manifest, Extension::NO_FLAGS,
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", &error);
// Fake gmail extension.
base::DictionaryValue manifest_gmail;
manifest_gmail.SetString(extensions::manifest_keys::kName,
"Gmail launcher controller test extension");
manifest_gmail.SetString(extensions::manifest_keys::kVersion, "1");
manifest_gmail.SetString(extensions::manifest_keys::kDescription,
"for testing pinned Gmail");
manifest_gmail.SetString(extensions::manifest_keys::kLaunchWebURL,
kGmailLaunchURL);
base::ListValue* list = new base::ListValue();
list->AppendString("*://mail.google.com/mail/ca");
manifest_gmail.Set(extensions::manifest_keys::kWebURLs, list);
extension3_ = Extension::Create(base::FilePath(), Manifest::UNPACKED,
manifest_gmail, Extension::NO_FLAGS,
extension_misc::kGmailAppId, &error);
// Fake google docs extension.
extension4_ = Extension::Create(base::FilePath(), Manifest::UNPACKED,
manifest, Extension::NO_FLAGS,
extension_misc::kGoogleDocAppId, &error);
extension5_ = Extension::Create(base::FilePath(), Manifest::UNPACKED,
manifest, Extension::NO_FLAGS,
"cccccccccccccccccccccccccccccccc", &error);
extension6_ = Extension::Create(base::FilePath(), Manifest::UNPACKED,
manifest, Extension::NO_FLAGS,
"dddddddddddddddddddddddddddddddd", &error);
extension7_ = Extension::Create(base::FilePath(), Manifest::UNPACKED,
manifest, Extension::NO_FLAGS,
"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", &error);
extension8_ = Extension::Create(base::FilePath(), Manifest::UNPACKED,
manifest, Extension::NO_FLAGS,
"ffffffffffffffffffffffffffffffff", &error);
extension_platform_app_ = Extension::Create(
base::FilePath(), Manifest::UNPACKED, manifest_platform_app,
Extension::NO_FLAGS, "gggggggggggggggggggggggggggggggg", &error);
arc_support_host_ = Extension::Create(base::FilePath(), Manifest::UNPACKED,
manifest, Extension::NO_FLAGS,
ArcSupportHost::kHostAppId, &error);
extension_service_->AddExtension(extension_chrome_.get());
}
// Creates a running platform V2 app (not pinned) of type |app_id|.
virtual void CreateRunningV2App(const std::string& app_id) {
DCHECK(!test_controller_);
// Change the created launcher controller into a V2 app controller.
std::unique_ptr<TestV2AppLauncherItemController> controller =
base::MakeUnique<TestV2AppLauncherItemController>(app_id);
test_controller_ = controller.get();
ash::ShelfID id = launcher_controller_->InsertAppLauncherItem(
std::move(controller), ash::STATUS_RUNNING, model_->item_count(),
ash::TYPE_APP);
DCHECK(launcher_controller_->IsPlatformApp(id));
}
// Sets the stage for a multi user test.
virtual void SetUpMultiUserScenario(syncer::SyncChangeList* user_a,
syncer::SyncChangeList* user_b) {
InitLauncherController();
EXPECT_EQ("AppList, Chrome", GetPinnedAppStatus());
// Set an empty pinned pref to begin with.
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 0, extension_misc::kChromeAppId);
SendPinChanges(sync_list, true);
EXPECT_EQ("AppList, Chrome", GetPinnedAppStatus());
// Assume all applications have been added already.
extension_service_->AddExtension(extension1_.get());
extension_service_->AddExtension(extension2_.get());
extension_service_->AddExtension(extension3_.get());
extension_service_->AddExtension(extension4_.get());
extension_service_->AddExtension(extension5_.get());
extension_service_->AddExtension(extension6_.get());
extension_service_->AddExtension(extension7_.get());
extension_service_->AddExtension(extension8_.get());
extension_service_->AddExtension(extension_platform_app_.get());
// There should be nothing in the list by now.
EXPECT_EQ("AppList, Chrome", GetPinnedAppStatus());
// Set user a preferences.
InsertAddPinChange(user_a, 0, extension1_->id());
InsertAddPinChange(user_a, 1, extension2_->id());
InsertAddPinChange(user_a, 2, extension3_->id());
InsertAddPinChange(user_a, 3, extension_platform_app_->id());
InsertAddPinChange(user_a, 4, extension4_->id());
InsertAddPinChange(user_a, 5, extension5_->id());
InsertAddPinChange(user_a, 6, extension_misc::kChromeAppId);
// Set user b preferences.
InsertAddPinChange(user_b, 0, extension6_->id());
InsertAddPinChange(user_b, 1, extension7_->id());
InsertAddPinChange(user_b, 2, extension8_->id());
InsertAddPinChange(user_b, 3, extension_misc::kChromeAppId);
}
void TearDown() override {
arc_test_.TearDown();
model_->RemoveObserver(model_observer_.get());
model_observer_.reset();
launcher_controller_ = nullptr;
BrowserWithTestWindowTest::TearDown();
}
BrowserWindow* CreateBrowserWindow() override {
return CreateTestBrowserWindowAura();
}
std::unique_ptr<Browser> CreateBrowserWithTestWindowForProfile(
Profile* profile) {
TestBrowserWindow* browser_window = CreateTestBrowserWindowAura();
new TestBrowserWindowOwner(browser_window);
return base::WrapUnique(
CreateBrowser(profile, Browser::TYPE_TABBED, false, browser_window));
}
void AddAppListLauncherItem() {
ash::ShelfItem app_list;
app_list.type = ash::TYPE_APP_LIST;
model_->Add(app_list);
}
// Create a launcher controller instance and register it as the ShelfDelegate.
// Returns a pointer to the uninitialized controller, which is owned by Shell.
ChromeLauncherControllerImpl* CreateLauncherController() {
// Shell owns ChromeLauncherController as its ShelfDelegate. The lifetime
// of this instance should match production behavior as closely as possible.
DCHECK(!ChromeLauncherController::instance());
std::unique_ptr<ChromeLauncherControllerImpl> launcher_controller =
base::MakeUnique<ChromeLauncherControllerImpl>(profile(), model_);
launcher_controller_ = launcher_controller.get();
ash::test::ShellTestApi().SetShelfDelegate(std::move(launcher_controller));
return launcher_controller_;
}
// Create and initialize the controller.
// Returns a pointer to the initialized controller, which is owned by Shell.
void InitLauncherController() { CreateLauncherController()->Init(); }
// Create and initialize the controller; create a tab and show the browser.
void InitLauncherControllerWithBrowser() {
InitLauncherController();
chrome::NewTab(browser());
browser()->window()->Show();
}
// Destroy Shell's controller instance and clear the local pointer.
void ResetLauncherController() {
launcher_controller_ = nullptr;
ash::test::ShellTestApi().SetShelfDelegate(nullptr);
}
// Destroy and recreate the controller; clear and reinitialize the ShelfModel.
// Returns a pointer to the uninitialized controller, which is owned by Shell.
// TODO(msw): This does not accurately represent ChromeLauncherControllerImpl
// lifetime or usage in production, and does not accurately simulate restarts.
ChromeLauncherControllerImpl* RecreateLauncherController() {
// Destroy any existing controller first; only one may exist at a time.
ResetLauncherController();
while (model_->item_count() > 0)
model_->RemoveItemAt(0);
AddAppListLauncherItem();
return CreateLauncherController();
}
void StartAppSyncService(const syncer::SyncDataList& init_sync_list) {
app_service_->MergeDataAndStartSyncing(
syncer::APP_LIST, init_sync_list,
base::MakeUnique<syncer::FakeSyncChangeProcessor>(),
base::MakeUnique<syncer::SyncErrorFactoryMock>());
EXPECT_EQ(init_sync_list.size(), app_service_->sync_items().size());
}
void StopAppSyncService() { app_service_->StopSyncing(syncer::APP_LIST); }
sync_preferences::PrefModelAssociator* GetPrefSyncService() {
sync_preferences::PrefServiceSyncable* pref_sync =
profile()->GetTestingPrefService();
sync_preferences::PrefModelAssociator* pref_sync_service =
reinterpret_cast<sync_preferences::PrefModelAssociator*>(
pref_sync->GetSyncableService(syncer::PREFERENCES));
return pref_sync_service;
}
void StartPrefSyncService(const syncer::SyncDataList& init_sync_list) {
syncer::SyncMergeResult r = GetPrefSyncService()->MergeDataAndStartSyncing(
syncer::PREFERENCES, init_sync_list,
base::MakeUnique<syncer::FakeSyncChangeProcessor>(),
base::MakeUnique<syncer::SyncErrorFactoryMock>());
EXPECT_FALSE(r.error().IsSet());
}
void StartPrefSyncServiceForPins(const base::ListValue& init_value) {
syncer::SyncDataList init_sync_list;
std::string serialized;
JSONStringValueSerializer json(&serialized);
json.Serialize(init_value);
sync_pb::EntitySpecifics one;
sync_pb::PreferenceSpecifics* pref_one = one.mutable_preference();
pref_one->set_name(prefs::kPinnedLauncherApps);
pref_one->set_value(serialized);
init_sync_list.push_back(syncer::SyncData::CreateRemoteData(
1, one, base::Time(), syncer::AttachmentIdList(),
syncer::AttachmentServiceProxyForTest::Create()));
StartPrefSyncService(init_sync_list);
}
void StopPrefSyncService() {
GetPrefSyncService()->StopSyncing(syncer::PREFERENCES);
}
void SetAppIconLoader(std::unique_ptr<AppIconLoader> loader) {
std::vector<std::unique_ptr<AppIconLoader>> loaders;
loaders.push_back(std::move(loader));
launcher_controller_->SetAppIconLoadersForTest(loaders);
}
void SetAppIconLoaders(std::unique_ptr<AppIconLoader> loader1,
std::unique_ptr<AppIconLoader> loader2) {
std::vector<std::unique_ptr<AppIconLoader>> loaders;
loaders.push_back(std::move(loader1));
loaders.push_back(std::move(loader2));
launcher_controller_->SetAppIconLoadersForTest(loaders);
}
void SetLauncherControllerHelper(LauncherControllerHelper* helper) {
launcher_controller_->SetLauncherControllerHelperForTest(
base::WrapUnique<LauncherControllerHelper>(helper));
}
void InsertPrefValue(base::ListValue* pref_value,
int index,
const std::string& extension_id) {
auto entry = base::MakeUnique<base::DictionaryValue>();
entry->SetString(ash::launcher::kPinnedAppsPrefAppIDPath, extension_id);
pref_value->Insert(index, std::move(entry));
}
void InsertRemoveAllPinsChange(syncer::SyncChangeList* list) {
for (const auto& sync_peer : app_service_->sync_items()) {
sync_pb::EntitySpecifics specifics;
sync_pb::AppListSpecifics* app_list_specifics =
specifics.mutable_app_list();
app_list_specifics->set_item_id(sync_peer.first);
app_list_specifics->set_item_type(sync_pb::AppListSpecifics::TYPE_APP);
syncer::SyncData sync_data =
syncer::SyncData::CreateLocalData(sync_peer.first, "Test", specifics);
list->push_back(syncer::SyncChange(
FROM_HERE, syncer::SyncChange::ACTION_DELETE, sync_data));
}
}
syncer::StringOrdinal GeneratePinPosition(int position) {
syncer::StringOrdinal ordinal_position =
syncer::StringOrdinal::CreateInitialOrdinal();
for (int i = 0; i < position; ++i)
ordinal_position = ordinal_position.CreateAfter();
return ordinal_position;
}
void InsertPinChange(syncer::SyncChangeList* list,
int position,
bool add_pin_change,
const std::string& app_id,
syncer::SyncChange::SyncChangeType type) {
sync_pb::EntitySpecifics specifics;
sync_pb::AppListSpecifics* app_list_specifics =
specifics.mutable_app_list();
app_list_specifics->set_item_id(app_id);
app_list_specifics->set_item_type(sync_pb::AppListSpecifics::TYPE_APP);
if (add_pin_change) {
if (position >= 0) {
app_list_specifics->set_item_pin_ordinal(
GeneratePinPosition(position).ToInternalValue());
} else {
app_list_specifics->set_item_pin_ordinal(std::string());
}
}
syncer::SyncData sync_data =
syncer::SyncData::CreateLocalData(app_id, "Test", specifics);
list->push_back(syncer::SyncChange(FROM_HERE, type, sync_data));
}
void InsertAddPinChange(syncer::SyncChangeList* list,
int position,
const std::string& app_id) {
InsertPinChange(list, position, true, app_id,
syncer::SyncChange::ACTION_ADD);
}
void InsertUpdatePinChange(syncer::SyncChangeList* list,
int position,
const std::string& app_id) {
InsertPinChange(list, position, true, app_id,
syncer::SyncChange::ACTION_UPDATE);
}
void InsertRemovePinChange(syncer::SyncChangeList* list,
const std::string& app_id) {
InsertPinChange(list, -1, true, app_id, syncer::SyncChange::ACTION_UPDATE);
}
void InsertLegacyPinChange(syncer::SyncChangeList* list,
const std::string& app_id) {
InsertPinChange(list, -1, false, app_id, syncer::SyncChange::ACTION_UPDATE);
}
void ResetPinModel() {
syncer::SyncChangeList sync_list;
InsertRemoveAllPinsChange(&sync_list);
InsertAddPinChange(&sync_list, 0, kDummyAppId);
app_service_->ProcessSyncChanges(FROM_HERE, sync_list);
}
void SendPinChanges(const syncer::SyncChangeList& sync_list,
bool reset_pin_model) {
if (!reset_pin_model) {
app_service_->ProcessSyncChanges(FROM_HERE, sync_list);
} else {
syncer::SyncChangeList combined_sync_list;
InsertRemoveAllPinsChange(&combined_sync_list);
combined_sync_list.insert(combined_sync_list.end(), sync_list.begin(),
sync_list.end());
app_service_->ProcessSyncChanges(FROM_HERE, combined_sync_list);
}
}
// Set the index at which the chrome icon should be.
void SetShelfChromeIconIndex(int index) {
DCHECK(
app_service_->GetPinPosition(extension_misc::kChromeAppId).IsValid());
syncer::StringOrdinal chrome_position;
chrome_position = index == 0 ? GeneratePinPosition(0).CreateBefore()
: GeneratePinPosition(index - 1).CreateBetween(
GeneratePinPosition(index));
syncer::SyncChangeList sync_list;
sync_pb::EntitySpecifics specifics;
sync_pb::AppListSpecifics* app_list_specifics =
specifics.mutable_app_list();
app_list_specifics->set_item_id(extension_misc::kChromeAppId);
app_list_specifics->set_item_type(sync_pb::AppListSpecifics::TYPE_APP);
app_list_specifics->set_item_pin_ordinal(chrome_position.ToInternalValue());
syncer::SyncData sync_data = syncer::SyncData::CreateLocalData(
extension_misc::kChromeAppId, "Test", specifics);
sync_list.push_back(syncer::SyncChange(
FROM_HERE, syncer::SyncChange::ACTION_UPDATE, sync_data));
app_service_->ProcessSyncChanges(FROM_HERE, sync_list);
}
// Gets the IDs of the currently pinned app items.
void GetPinnedAppIds(ChromeLauncherControllerImpl* controller,
std::vector<std::string>* app_ids) {
app_ids->clear();
for (const auto& item : model_->items()) {
if (item.type == ash::TYPE_PINNED_APP)
app_ids->push_back(item.app_launch_id.app_id());
}
}
// Get the setup of the currently shown launcher items in one string.
// Each pinned element will start with a big letter, each running but not
// pinned V1 app will start with a small letter and each running but not
// pinned V2 app will start with a '*' + small letter.
std::string GetPinnedAppStatus() {
std::string result;
for (int i = 0; i < model_->item_count(); i++) {
if (!result.empty())
result.append(", ");
switch (model_->items()[i].type) {
case ash::TYPE_APP: {
if (launcher_controller_->IsPlatformApp(model_->items()[i].id))
result += "*";
const std::string& app =
launcher_controller_->GetAppIDForShelfID(model_->items()[i].id);
EXPECT_FALSE(launcher_controller_->IsAppPinned(app));
if (app == extension1_->id()) {
result += "app1";
} else if (app == extension2_->id()) {
result += "app2";
} else if (app == extension3_->id()) {
result += "app3";
} else if (app == extension4_->id()) {
result += "app4";
} else if (app == extension5_->id()) {
result += "app5";
} else if (app == extension6_->id()) {
result += "app6";
} else if (app == extension7_->id()) {
result += "app7";
} else if (app == extension8_->id()) {
result += "app8";
} else if (app == extension_platform_app_->id()) {
result += "platform_app";
} else {
result += "unknown";
}
break;
}
case ash::TYPE_PINNED_APP: {
if (launcher_controller_->IsPlatformApp(model_->items()[i].id))
result += "*";
const std::string& app =
launcher_controller_->GetAppIDForShelfID(model_->items()[i].id);
EXPECT_TRUE(launcher_controller_->IsAppPinned(app));
if (app == extension1_->id()) {
result += "App1";
} else if (app == extension2_->id()) {
result += "App2";
} else if (app == extension3_->id()) {
result += "App3";
} else if (app == extension4_->id()) {
result += "App4";
} else if (app == extension5_->id()) {
result += "App5";
} else if (app == extension6_->id()) {
result += "App6";
} else if (app == extension7_->id()) {
result += "App7";
} else if (app == extension8_->id()) {
result += "App8";
} else if (app == extension_platform_app_->id()) {
result += "Platform_App";
} else if (app == arc_support_host_->id()) {
result += "Play Store";
} else {
bool arc_app_found = false;
for (const auto& arc_app : arc_test_.fake_apps()) {
if (app == ArcAppTest::GetAppId(arc_app)) {
result += arc_app.name;
arc_app_found = true;
break;
}
}
if (!arc_app_found)
result += "unknown";
}
break;
}
case ash::TYPE_BROWSER_SHORTCUT:
result += "Chrome";
break;
case ash::TYPE_APP_LIST:
result += "AppList";
break;
default:
result += "Unknown";
break;
}
}
return result;
}
// Remember the order of unpinned but running applications for the current
// user.
void RememberUnpinnedRunningApplicationOrder() {
launcher_controller_->RememberUnpinnedRunningApplicationOrder();
}
// Restore the order of running but unpinned applications for a given user.
void RestoreUnpinnedRunningApplicationOrder(const AccountId& account_id) {
launcher_controller_->RestoreUnpinnedRunningApplicationOrder(
account_id.GetUserEmail());
}
void SendListOfArcApps() {
arc_test_.app_instance()->RefreshAppList();
arc_test_.app_instance()->SendRefreshAppList(arc_test_.fake_apps());
}
void SendListOfArcShortcuts() {
arc_test_.app_instance()->SendInstallShortcuts(arc_test_.fake_shortcuts());
}
void UninstallArcApps() {
arc_test_.app_instance()->RefreshAppList();
arc_test_.app_instance()->SendRefreshAppList(
std::vector<arc::mojom::AppInfo>());
}
// TODO(victorhsieh): Add test coverage for when ARC is started regardless
// Play Store opt-in status, and the followed opt-in and opt-out.
void EnablePlayStore(bool enabled) {
arc::SetArcPlayStoreEnabledForProfile(profile(), enabled);
base::RunLoop().RunUntilIdle();
}
void EnableTabletMode(bool enable) {
ash::MaximizeModeController* controller =
ash::Shell::Get()->maximize_mode_controller();
controller->EnableMaximizeModeWindowManager(enable);
}
void ValidateArcState(bool arc_enabled,
bool arc_managed,
arc::ArcSessionManager::State state,
const std::string& pin_status) {
EXPECT_EQ(arc_enabled, arc::IsArcPlayStoreEnabledForProfile(profile()));
EXPECT_EQ(arc_managed,
arc::IsArcPlayStoreEnabledPreferenceManagedForProfile(profile()));
EXPECT_EQ(state, arc_test_.arc_session_manager()->state());
EXPECT_EQ(pin_status, GetPinnedAppStatus());
}
// Creates app window and set optional ARC application id.
views::Widget* CreateArcWindow(const std::string& window_app_id) {
views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
params.bounds = gfx::Rect(5, 5, 20, 20);
params.context = GetContext();
views::Widget* widget = new views::Widget();
widget->Init(params);
// Set ARC id before showing the window to be recognized in
// ArcAppWindowLauncherController.
exo::ShellSurface::SetApplicationId(widget->GetNativeWindow(),
window_app_id);
widget->Show();
widget->Activate();
return widget;
}
arc::mojom::AppInfo CreateAppInfo(const std::string& name,
const std::string& activity,
const std::string& package_name,
OrientationLock lock) {
arc::mojom::AppInfo appinfo;
appinfo.name = name;
appinfo.package_name = package_name;
appinfo.activity = activity;
appinfo.orientation_lock = lock;
return appinfo;
}
std::string AddArcAppAndShortcut(const arc::mojom::AppInfo& app_info) {
ArcAppListPrefs* const prefs = arc_test_.arc_app_list_prefs();
// Adding app to the prefs, and check that the app is accessible by id.
prefs->AddAppAndShortcut(
true /* app_ready */, app_info.name, app_info.package_name,
app_info.activity, std::string() /* intent_uri */,
std::string() /* icon_resource_id */, false /* sticky */,
true /* notifications_enabled */, false /* shortcut */,
true /* launchable */, app_info.orientation_lock);
const std::string app_id =
ArcAppListPrefs::GetAppId(app_info.package_name, app_info.activity);
EXPECT_TRUE(prefs->GetApp(app_id));
return app_id;
}
void NotifyOnTaskCreated(const arc::mojom::AppInfo& appinfo,
int32_t task_id) {
ArcAppListPrefs* const prefs = arc_test_.arc_app_list_prefs();
prefs->OnTaskCreated(task_id, appinfo.package_name, appinfo.activity,
appinfo.name, std::string());
}
void NotifyOnTaskOrientationLockRequested(int32_t task_id,
OrientationLock lock) {
ArcAppListPrefs* const prefs = arc_test_.arc_app_list_prefs();
prefs->OnTaskOrientationLockRequested(task_id, lock);
}
// Needed for extension service & friends to work.
scoped_refptr<Extension> extension_chrome_;
scoped_refptr<Extension> extension1_;
scoped_refptr<Extension> extension2_;
scoped_refptr<Extension> extension3_;
scoped_refptr<Extension> extension4_;
scoped_refptr<Extension> extension5_;
scoped_refptr<Extension> extension6_;
scoped_refptr<Extension> extension7_;
scoped_refptr<Extension> extension8_;
scoped_refptr<Extension> extension_platform_app_;
scoped_refptr<Extension> arc_support_host_;
ArcAppTest arc_test_;
bool auto_start_arc_test_ = false;
ChromeLauncherControllerImpl* launcher_controller_ = nullptr;
std::unique_ptr<TestShelfModelObserver> model_observer_;
ash::ShelfModel* model_ = nullptr;
std::unique_ptr<TestingProfileManager> profile_manager_;
// |item_delegate_manager_| owns |test_controller_|.
ash::ShelfItemDelegate* test_controller_ = nullptr;
ExtensionService* extension_service_ = nullptr;
app_list::AppListSyncableService* app_service_ = nullptr;
private:
TestBrowserWindow* CreateTestBrowserWindowAura() {
std::unique_ptr<aura::Window> window(new aura::Window(nullptr));
window->set_id(0);
window->SetType(ui::wm::WINDOW_TYPE_NORMAL);
window->Init(ui::LAYER_TEXTURED);
aura::client::ParentWindowWithContext(window.get(), GetContext(),
gfx::Rect(200, 200));
return new TestBrowserWindowAura(std::move(window));
}
DISALLOW_COPY_AND_ASSIGN(ChromeLauncherControllerImplTest);
};
class ChromeLauncherControllerImplWithArcTest
: public ChromeLauncherControllerImplTest,
public ::testing::WithParamInterface<bool> {
protected:
ChromeLauncherControllerImplWithArcTest() { auto_start_arc_test_ = true; }
~ChromeLauncherControllerImplWithArcTest() override {}
void SetUp() override {
if (GetParam())
arc::SetArcAlwaysStartForTesting();
ChromeLauncherControllerImplTest::SetUp();
}
private:
DISALLOW_COPY_AND_ASSIGN(ChromeLauncherControllerImplWithArcTest);
};
INSTANTIATE_TEST_CASE_P(,
ChromeLauncherControllerImplWithArcTest,
::testing::Bool());
// Watches WebContents and blocks until it is destroyed. This is needed for
// the destruction of a V2 application.
class WebContentsDestroyedWatcher : public content::WebContentsObserver {
public:
explicit WebContentsDestroyedWatcher(content::WebContents* web_contents)
: content::WebContentsObserver(web_contents),
message_loop_runner_(new content::MessageLoopRunner) {
EXPECT_TRUE(web_contents != NULL);
}
~WebContentsDestroyedWatcher() override {}
// Waits until the WebContents is destroyed.
void Wait() {
message_loop_runner_->Run();
}
private:
// Overridden WebContentsObserver methods.
void WebContentsDestroyed() override { message_loop_runner_->Quit(); }
scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
DISALLOW_COPY_AND_ASSIGN(WebContentsDestroyedWatcher);
};
// A V1 windowed application.
class V1App : public TestBrowserWindow {
public:
V1App(Profile* profile, const std::string& app_name) {
Browser::CreateParams params = Browser::CreateParams::CreateForApp(
kCrxAppPrefix + app_name, true /* trusted_source */, gfx::Rect(),
profile, true);
params.window = this;
browser_.reset(new Browser(params));
chrome::AddTabAt(browser_.get(), GURL(), 0, true);
}
~V1App() override {
// close all tabs. Note that we do not need to destroy the browser itself.
browser_->tab_strip_model()->CloseAllTabs();
}
Browser* browser() { return browser_.get(); }
private:
// The associated browser with this app.
std::unique_ptr<Browser> browser_;
DISALLOW_COPY_AND_ASSIGN(V1App);
};
// A V2 application window created with an |extension| and for a |profile|.
// Upon destruction it will properly close the application; supports panels too.
class V2App {
public:
V2App(Profile* profile,
const extensions::Extension* extension,
extensions::AppWindow::WindowType window_type =
extensions::AppWindow::WINDOW_TYPE_DEFAULT)
: creator_web_contents_(
content::WebContentsTester::CreateTestWebContents(profile,
nullptr)) {
window_ = new extensions::AppWindow(profile, new ChromeAppDelegate(true),
extension);
extensions::AppWindow::CreateParams params;
params.window_type = window_type;
// Note: normally, the creator RFH is the background page of the
// app/extension
// calling chrome.app.window.create. For unit testing purposes, just passing
// in a random RenderFrameHost is Good Enoughâ„¢.
window_->Init(GURL(std::string()),
new extensions::AppWindowContentsImpl(window_),
creator_web_contents_->GetMainFrame(), params);
}
virtual ~V2App() {
WebContentsDestroyedWatcher destroyed_watcher(window_->web_contents());
window_->GetBaseWindow()->Close();
destroyed_watcher.Wait();
}
extensions::AppWindow* window() { return window_; }
private:
std::unique_ptr<content::WebContents> creator_web_contents_;
// The app window which represents the application. Note that the window
// deletes itself asynchronously after window_->GetBaseWindow()->Close() gets
// called.
extensions::AppWindow* window_;
DISALLOW_COPY_AND_ASSIGN(V2App);
};
// The testing framework to test multi profile scenarios.
class MultiProfileMultiBrowserShelfLayoutChromeLauncherControllerImplTest
: public ChromeLauncherControllerImplTest {
protected:
MultiProfileMultiBrowserShelfLayoutChromeLauncherControllerImplTest() {}
~MultiProfileMultiBrowserShelfLayoutChromeLauncherControllerImplTest()
override {}
// Overwrite the Setup function to enable multi profile and needed objects.
void SetUp() override {
profile_manager_.reset(
new TestingProfileManager(TestingBrowserProcess::GetGlobal()));
ASSERT_TRUE(profile_manager_->SetUp());
// AvatarMenu and multiple profiles works after user logged in.
profile_manager_->SetLoggedIn(true);
// Initialize the UserManager singleton to a fresh FakeUserManager instance.
user_manager_enabler_.reset(new chromeos::ScopedUserManagerEnabler(
new chromeos::FakeChromeUserManager));
// Initialize the WallpaperManager singleton.
chromeos::WallpaperManager::Initialize();
// Initialize the rest.
ChromeLauncherControllerImplTest::SetUp();
// Get some base objects.
shell_delegate_ = static_cast<ash::test::TestShellDelegate*>(
ash::Shell::Get()->shell_delegate());
shell_delegate_->set_multi_profiles_enabled(true);
}
void TearDown() override {
ChromeLauncherControllerImplTest::TearDown();
user_manager_enabler_.reset();
for (ProfileToNameMap::iterator it = created_profiles_.begin();
it != created_profiles_.end(); ++it)
profile_manager_->DeleteTestingProfile(it->second);
chromeos::WallpaperManager::Shutdown();
// A Task is leaked if we don't destroy everything, then run the message
// loop.
base::RunLoop().RunUntilIdle();
}
// Creates a profile for a given |user_name|. Note that this class will keep
// the ownership of the created object.
TestingProfile* CreateMultiUserProfile(const std::string& user_name) {
const std::string email_string = user_name + "@example.com";
const AccountId account_id(AccountId::FromUserEmail(email_string));
// Add a user to the fake user manager.
GetFakeUserManager()->AddUser(account_id);
GetFakeUserManager()->LoginUser(account_id);
TestingProfile* profile =
profile_manager()->CreateTestingProfile(account_id.GetUserEmail());
EXPECT_TRUE(profile);
// Remember the profile name so that we can destroy it upon destruction.
created_profiles_[profile] = account_id.GetUserEmail();
if (chrome::MultiUserWindowManager::GetInstance())
chrome::MultiUserWindowManager::GetInstance()->AddUser(profile);
if (launcher_controller_)
launcher_controller_->AdditionalUserAddedToSession(profile);
return profile;
}
// Switch to another user.
void SwitchActiveUser(const AccountId& account_id) {
GetFakeUserManager()->SwitchActiveUser(account_id);
chrome::MultiUserWindowManagerChromeOS* manager =
static_cast<chrome::MultiUserWindowManagerChromeOS*>(
chrome::MultiUserWindowManager::GetInstance());
manager->SetAnimationSpeedForTest(
chrome::MultiUserWindowManagerChromeOS::ANIMATION_SPEED_DISABLED);
manager->ActiveUserChanged(GetFakeUserManager()->FindUser(account_id));
launcher_controller_->browser_status_monitor_for_test()->ActiveUserChanged(
account_id.GetUserEmail());
for (const auto& controller :
launcher_controller_->app_window_controllers_for_test()) {
controller->ActiveUserChanged(account_id.GetUserEmail());
}
}
// Creates a browser with a |profile| and load a tab with a |title| and |url|.
std::unique_ptr<Browser> CreateBrowserAndTabWithProfile(
Profile* profile,
const std::string& title,
const std::string& url) {
std::unique_ptr<Browser> browser(
CreateBrowserWithTestWindowForProfile(profile));
chrome::NewTab(browser.get());
browser->window()->Show();
NavigateAndCommitActiveTabWithTitle(browser.get(), GURL(url),
ASCIIToUTF16(title));
return browser;
}
// Creates a running V1 application.
// Note that with the use of the launcher_controller_helper as done below,
// this is only usable with a single v1 application.
V1App* CreateRunningV1App(Profile* profile,
const std::string& app_name,
const std::string& url) {
V1App* v1_app = new V1App(profile, app_name);
NavigateAndCommitActiveTabWithTitle(v1_app->browser(), GURL(url),
base::string16());
return v1_app;
}
ash::test::TestShellDelegate* shell_delegate() { return shell_delegate_; }
// Override BrowserWithTestWindowTest:
TestingProfile* CreateProfile() override {
return CreateMultiUserProfile("user1");
}
void DestroyProfile(TestingProfile* profile) override {
// Delete the profile through our profile manager.
ProfileToNameMap::iterator it = created_profiles_.find(profile);
DCHECK(it != created_profiles_.end());
profile_manager_->DeleteTestingProfile(it->second);
created_profiles_.erase(it);
}
private:
typedef std::map<Profile*, std::string> ProfileToNameMap;
TestingProfileManager* profile_manager() { return profile_manager_.get(); }
chromeos::FakeChromeUserManager* GetFakeUserManager() {
return static_cast<chromeos::FakeChromeUserManager*>(
user_manager::UserManager::Get());
}
std::unique_ptr<chromeos::ScopedUserManagerEnabler> user_manager_enabler_;
ash::test::TestShellDelegate* shell_delegate_;
ProfileToNameMap created_profiles_;
DISALLOW_COPY_AND_ASSIGN(
MultiProfileMultiBrowserShelfLayoutChromeLauncherControllerImplTest);
};
class ChromeLauncherControllerImplMultiProfileWithArcTest
: public MultiProfileMultiBrowserShelfLayoutChromeLauncherControllerImplTest, // NOLINT(whitespace/line_length)
public ::testing::WithParamInterface<bool> {
protected:
ChromeLauncherControllerImplMultiProfileWithArcTest() {
auto_start_arc_test_ = true;
}
~ChromeLauncherControllerImplMultiProfileWithArcTest() override {}
void SetUp() override {
if (GetParam())
arc::SetArcAlwaysStartForTesting();
MultiProfileMultiBrowserShelfLayoutChromeLauncherControllerImplTest::
SetUp();
}
private:
DISALLOW_COPY_AND_ASSIGN(ChromeLauncherControllerImplMultiProfileWithArcTest);
};
INSTANTIATE_TEST_CASE_P(,
ChromeLauncherControllerImplMultiProfileWithArcTest,
::testing::Bool());
TEST_F(ChromeLauncherControllerImplTest, DefaultApps) {
InitLauncherController();
// The model should only contain the browser shortcut and app list items.
EXPECT_EQ(2, model_->item_count());
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension2_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension3_->id()));
// Installing |extension3_| should add it to the launcher - behind the
// chrome icon.
extension_service_->AddExtension(extension3_.get());
EXPECT_EQ("AppList, Chrome, App3", GetPinnedAppStatus());
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension2_->id()));
}
TEST_P(ChromeLauncherControllerImplWithArcTest,
ArcAppPinCrossPlatformWorkflow) {
// Work on ARC disabled platform first.
const std::string arc_app_id1 =
ArcAppTest::GetAppId(arc_test_.fake_apps()[0]);
const std::string arc_app_id2 =
ArcAppTest::GetAppId(arc_test_.fake_apps()[1]);
const std::string arc_app_id3 =
ArcAppTest::GetAppId(arc_test_.fake_apps()[2]);
InitLauncherController();
extension_service_->AddExtension(extension1_.get());
extension_service_->AddExtension(extension2_.get());
extension_service_->AddExtension(extension3_.get());
// extension 1, 3 are pinned by user
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 0, extension1_->id());
InsertAddPinChange(&sync_list, 1, arc_app_id1);
InsertAddPinChange(&sync_list, 2, extension2_->id());
InsertAddPinChange(&sync_list, 3, arc_app_id2);
InsertAddPinChange(&sync_list, 4, extension3_->id());
SendPinChanges(sync_list, true);
SetShelfChromeIconIndex(1);
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(arc_app_id1));
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension2_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(arc_app_id2));
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension3_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(arc_app_id3));
EXPECT_EQ("AppList, App1, Chrome, App2, App3", GetPinnedAppStatus());
// Persist pin state, we don't have active pin for ARC apps yet, but pin
// model should have it.
syncer::SyncDataList copy_sync_list =
app_service_->GetAllSyncData(syncer::APP_LIST);
ResetLauncherController();
SendPinChanges(syncer::SyncChangeList(), true);
StopAppSyncService();
EXPECT_EQ(0U, app_service_->sync_items().size());
// Move to ARC enabled platform, restart syncing with stored data.
StartAppSyncService(copy_sync_list);
RecreateLauncherController()->Init();
// Pins must be automatically updated.
SendListOfArcApps();
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_TRUE(launcher_controller_->IsAppPinned(arc_app_id1));
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension2_->id()));
EXPECT_TRUE(launcher_controller_->IsAppPinned(arc_app_id2));
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension3_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(arc_app_id3));
EXPECT_EQ("AppList, App1, Chrome, Fake App 0, App2, Fake App 1, App3",
GetPinnedAppStatus());
// Now move pins on ARC enabled platform.
model_->Move(1, 4);
model_->Move(3, 1);
model_->Move(3, 5);
model_->Move(4, 2);
EXPECT_EQ("AppList, App2, Fake App 1, Chrome, App1, Fake App 0, App3",
GetPinnedAppStatus());
copy_sync_list = app_service_->GetAllSyncData(syncer::APP_LIST);
ResetLauncherController();
ResetPinModel();
SendPinChanges(syncer::SyncChangeList(), true);
StopAppSyncService();
EXPECT_EQ(0U, app_service_->sync_items().size());
// Move back to ARC disabled platform.
// TODO(victorhsieh): Implement opt-out.
if (arc::ShouldArcAlwaysStart())
return;
EnablePlayStore(false);
StartAppSyncService(copy_sync_list);
RecreateLauncherController()->Init();
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(arc_app_id1));
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension2_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(arc_app_id2));
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension3_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(arc_app_id3));
EXPECT_EQ("AppList, App2, Chrome, App1, App3", GetPinnedAppStatus());
// Now move/remove pins on ARC disabled platform.
model_->Move(4, 2);
launcher_controller_->UnpinAppWithID(extension2_->id());
EXPECT_EQ("AppList, App3, Chrome, App1", GetPinnedAppStatus());
EnablePlayStore(true);
SendListOfArcApps();
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_TRUE(launcher_controller_->IsAppPinned(arc_app_id1));
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension2_->id()));
EXPECT_TRUE(launcher_controller_->IsAppPinned(arc_app_id2));
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension3_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(arc_app_id3));
EXPECT_EQ("AppList, Fake App 1, App3, Chrome, App1, Fake App 0",
GetPinnedAppStatus());
}
/*
* Test ChromeLauncherControllerImpl correctly merges policy pinned apps
* and user pinned apps
*/
TEST_F(ChromeLauncherControllerImplTest, MergePolicyAndUserPrefPinnedApps) {
InitLauncherController();
extension_service_->AddExtension(extension1_.get());
extension_service_->AddExtension(extension3_.get());
extension_service_->AddExtension(extension4_.get());
extension_service_->AddExtension(extension5_.get());
// extension 1, 3 are pinned by user
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 0, extension1_->id());
InsertAddPinChange(&sync_list, 1, extension_misc::kChromeAppId);
InsertAddPinChange(&sync_list, 2, extension3_->id());
SendPinChanges(sync_list, true);
base::ListValue policy_value;
// extension 2 4 are pinned by policy
InsertPrefValue(&policy_value, 0, extension2_->id());
InsertPrefValue(&policy_value, 1, extension4_->id());
profile()->GetTestingPrefService()->SetManagedPref(
prefs::kPolicyPinnedLauncherApps, policy_value.CreateDeepCopy());
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
// 2 is not pinned as it's not installed
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension2_->id()));
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension3_->id()));
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension4_->id()));
// install extension 2 and check
extension_service_->AddExtension(extension2_.get());
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension2_->id()));
// Check user can manually pin or unpin these apps
EXPECT_EQ(AppListControllerDelegate::PIN_EDITABLE,
GetPinnableForAppID(extension1_->id(), profile()));
EXPECT_EQ(AppListControllerDelegate::PIN_FIXED,
GetPinnableForAppID(extension2_->id(), profile()));
EXPECT_EQ(AppListControllerDelegate::PIN_EDITABLE,
GetPinnableForAppID(extension3_->id(), profile()));
EXPECT_EQ(AppListControllerDelegate::PIN_FIXED,
GetPinnableForAppID(extension4_->id(), profile()));
// Check the order of shelf pinned apps
EXPECT_EQ("AppList, App2, App4, App1, Chrome, App3", GetPinnedAppStatus());
}
// Check that the restauration of launcher items is happening in the same order
// as the user has pinned them (on another system) when they are synced reverse
// order.
TEST_F(ChromeLauncherControllerImplTest, RestoreDefaultAppsReverseOrder) {
InitLauncherController();
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 0, extension1_->id());
InsertAddPinChange(&sync_list, 1, extension2_->id());
InsertAddPinChange(&sync_list, 2, extension3_->id());
SendPinChanges(sync_list, true);
// The model should only contain the browser shortcut and app list items.
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension2_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension3_->id()));
EXPECT_EQ("AppList, Chrome", GetPinnedAppStatus());
// Installing |extension3_| should add it to the shelf - behind the
// chrome icon.
ash::ShelfItem item;
extension_service_->AddExtension(extension3_.get());
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension2_->id()));
EXPECT_EQ("AppList, Chrome, App3", GetPinnedAppStatus());
// Installing |extension2_| should add it to the launcher - behind the
// chrome icon, but in first location.
extension_service_->AddExtension(extension2_.get());
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_EQ("AppList, Chrome, App2, App3", GetPinnedAppStatus());
// Installing |extension1_| should add it to the launcher - behind the
// chrome icon, but in first location.
extension_service_->AddExtension(extension1_.get());
EXPECT_EQ("AppList, Chrome, App1, App2, App3", GetPinnedAppStatus());
}
// Check that the restauration of launcher items is happening in the same order
// as the user has pinned them (on another system) when they are synced random
// order.
TEST_F(ChromeLauncherControllerImplTest, RestoreDefaultAppsRandomOrder) {
InitLauncherController();
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 0, extension1_->id());
InsertAddPinChange(&sync_list, 1, extension2_->id());
InsertAddPinChange(&sync_list, 2, extension3_->id());
SendPinChanges(sync_list, true);
// The model should only contain the browser shortcut and app list items.
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension2_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension3_->id()));
EXPECT_EQ("AppList, Chrome", GetPinnedAppStatus());
// Installing |extension2_| should add it to the launcher - behind the
// chrome icon.
extension_service_->AddExtension(extension2_.get());
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension3_->id()));
EXPECT_EQ("AppList, Chrome, App2", GetPinnedAppStatus());
// Installing |extension1_| should add it to the launcher - behind the
// chrome icon, but in first location.
extension_service_->AddExtension(extension1_.get());
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension3_->id()));
EXPECT_EQ("AppList, Chrome, App1, App2", GetPinnedAppStatus());
// Installing |extension3_| should add it to the launcher - behind the
// chrome icon, but in first location.
extension_service_->AddExtension(extension3_.get());
EXPECT_EQ("AppList, Chrome, App1, App2, App3", GetPinnedAppStatus());
}
// Check that the restauration of launcher items is happening in the same order
// as the user has pinned / moved them (on another system) when they are synced
// random order - including the chrome icon.
TEST_F(ChromeLauncherControllerImplTest,
RestoreDefaultAppsRandomOrderChromeMoved) {
InitLauncherController();
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 0, extension1_->id());
InsertAddPinChange(&sync_list, 1, extension_misc::kChromeAppId);
InsertAddPinChange(&sync_list, 2, extension2_->id());
InsertAddPinChange(&sync_list, 3, extension3_->id());
SendPinChanges(sync_list, true);
// The model should only contain the browser shortcut and app list items.
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension2_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension3_->id()));
EXPECT_EQ("AppList, Chrome", GetPinnedAppStatus());
// Installing |extension2_| should add it to the shelf - behind the
// chrome icon.
ash::ShelfItem item;
extension_service_->AddExtension(extension2_.get());
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension3_->id()));
EXPECT_EQ("AppList, Chrome, App2", GetPinnedAppStatus());
// Installing |extension1_| should add it to the launcher - behind the
// chrome icon, but in first location.
extension_service_->AddExtension(extension1_.get());
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension3_->id()));
EXPECT_EQ("AppList, App1, Chrome, App2", GetPinnedAppStatus());
// Installing |extension3_| should add it to the launcher - behind the
// chrome icon, but in first location.
extension_service_->AddExtension(extension3_.get());
EXPECT_EQ("AppList, App1, Chrome, App2, App3", GetPinnedAppStatus());
}
// Check that syncing to a different state does the correct thing.
TEST_F(ChromeLauncherControllerImplTest, RestoreDefaultAppsResyncOrder) {
InitLauncherController();
syncer::SyncChangeList sync_list0;
InsertAddPinChange(&sync_list0, 0, extension1_->id());
InsertAddPinChange(&sync_list0, 1, extension2_->id());
InsertAddPinChange(&sync_list0, 2, extension3_->id());
SendPinChanges(sync_list0, true);
// The shelf layout has always one static item at the beginning (App List).
extension_service_->AddExtension(extension2_.get());
EXPECT_EQ("AppList, Chrome, App2", GetPinnedAppStatus());
extension_service_->AddExtension(extension1_.get());
EXPECT_EQ("AppList, Chrome, App1, App2", GetPinnedAppStatus());
extension_service_->AddExtension(extension3_.get());
EXPECT_EQ("AppList, Chrome, App1, App2, App3", GetPinnedAppStatus());
// Change the order with increasing chrome position and decreasing position.
syncer::SyncChangeList sync_list1;
InsertAddPinChange(&sync_list1, 0, extension3_->id());
InsertAddPinChange(&sync_list1, 1, extension1_->id());
InsertAddPinChange(&sync_list1, 2, extension2_->id());
InsertAddPinChange(&sync_list1, 3, extension_misc::kChromeAppId);
SendPinChanges(sync_list1, true);
EXPECT_EQ("AppList, App3, App1, App2, Chrome", GetPinnedAppStatus());
syncer::SyncChangeList sync_list2;
InsertAddPinChange(&sync_list2, 0, extension2_->id());
InsertAddPinChange(&sync_list2, 1, extension3_->id());
InsertAddPinChange(&sync_list2, 2, extension_misc::kChromeAppId);
InsertAddPinChange(&sync_list2, 3, extension1_->id());
SendPinChanges(sync_list2, true);
EXPECT_EQ("AppList, App2, App3, Chrome, App1", GetPinnedAppStatus());
// Check that the chrome icon can also be at the first possible location.
syncer::SyncChangeList sync_list3;
InsertAddPinChange(&sync_list3, 0, extension3_->id());
InsertAddPinChange(&sync_list3, 1, extension2_->id());
InsertAddPinChange(&sync_list3, 2, extension1_->id());
SendPinChanges(sync_list3, true);
EXPECT_EQ("AppList, Chrome, App3, App2, App1", GetPinnedAppStatus());
// Check that unloading of extensions works as expected.
extension_service_->UnloadExtension(extension1_->id(),
UnloadedExtensionInfo::REASON_UNINSTALL);
EXPECT_EQ("AppList, Chrome, App3, App2", GetPinnedAppStatus());
extension_service_->UnloadExtension(extension2_->id(),
UnloadedExtensionInfo::REASON_UNINSTALL);
EXPECT_EQ("AppList, Chrome, App3", GetPinnedAppStatus());
// Check that an update of an extension does not crash the system.
extension_service_->UnloadExtension(extension3_->id(),
UnloadedExtensionInfo::REASON_UPDATE);
EXPECT_EQ("AppList, Chrome, App3", GetPinnedAppStatus());
}
// Test the V1 app interaction flow: run it, activate it, close it.
TEST_F(ChromeLauncherControllerImplTest, V1AppRunActivateClose) {
InitLauncherController();
// The model should only contain the browser shortcut and app list items.
EXPECT_EQ(2, model_->item_count());
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(extension1_->id()));
// Reporting that the app is running should create a new shelf item.
launcher_controller_->SetV1AppStatus(extension1_->id(), ash::STATUS_RUNNING);
EXPECT_EQ(3, model_->item_count());
EXPECT_EQ(ash::TYPE_APP, model_->items()[2].type);
EXPECT_EQ(ash::STATUS_RUNNING, model_->items()[2].status);
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(extension1_->id()));
// Reporting an active status should just update the existing item.
launcher_controller_->SetV1AppStatus(extension1_->id(), ash::STATUS_ACTIVE);
EXPECT_EQ(3, model_->item_count());
EXPECT_EQ(ash::STATUS_ACTIVE, model_->items()[2].status);
// Reporting that the app is closed should remove its shelf item.
launcher_controller_->SetV1AppStatus(extension1_->id(), ash::STATUS_CLOSED);
EXPECT_EQ(2, model_->item_count());
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(extension1_->id()));
// Reporting that the app is closed again should have no effect.
launcher_controller_->SetV1AppStatus(extension1_->id(), ash::STATUS_CLOSED);
EXPECT_EQ(2, model_->item_count());
}
// Test the V1 app interaction flow: pin it, run it, close it, unpin it.
TEST_F(ChromeLauncherControllerImplTest, V1AppPinRunCloseUnpin) {
InitLauncherController();
// The model should only contain the browser shortcut and app list items.
EXPECT_EQ(2, model_->item_count());
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(extension1_->id()));
// Pinning the app should create a new shelf item.
launcher_controller_->PinAppWithID(extension1_->id());
EXPECT_EQ(3, model_->item_count());
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[2].type);
EXPECT_EQ(ash::STATUS_CLOSED, model_->items()[2].status);
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(extension1_->id()));
// Reporting that the app is running should just update the existing item.
launcher_controller_->SetV1AppStatus(extension1_->id(), ash::STATUS_RUNNING);
EXPECT_EQ(3, model_->item_count());
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[2].type);
EXPECT_EQ(ash::STATUS_RUNNING, model_->items()[2].status);
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(extension1_->id()));
// Reporting that the app is closed should just update the existing item.
launcher_controller_->SetV1AppStatus(extension1_->id(), ash::STATUS_CLOSED);
EXPECT_EQ(3, model_->item_count());
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[2].type);
EXPECT_EQ(ash::STATUS_CLOSED, model_->items()[2].status);
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(extension1_->id()));
// Unpinning the app should remove its shelf item.
launcher_controller_->UnpinAppWithID(extension1_->id());
EXPECT_EQ(2, model_->item_count());
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(extension1_->id()));
}
// Test the V1 app interaction flow: run it, pin it, close it, unpin it.
TEST_F(ChromeLauncherControllerImplTest, V1AppRunPinCloseUnpin) {
InitLauncherController();
// The model should only contain the browser shortcut and app list items.
EXPECT_EQ(2, model_->item_count());
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(extension1_->id()));
// Reporting that the app is running should create a new shelf item.
launcher_controller_->SetV1AppStatus(extension1_->id(), ash::STATUS_RUNNING);
EXPECT_EQ(3, model_->item_count());
EXPECT_EQ(ash::TYPE_APP, model_->items()[2].type);
EXPECT_EQ(ash::STATUS_RUNNING, model_->items()[2].status);
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(extension1_->id()));
// Pinning the app should just update the existing item.
launcher_controller_->PinAppWithID(extension1_->id());
EXPECT_EQ(3, model_->item_count());
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[2].type);
EXPECT_EQ(ash::STATUS_RUNNING, model_->items()[2].status);
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(extension1_->id()));
// Reporting that the app is closed should just update the existing item.
launcher_controller_->SetV1AppStatus(extension1_->id(), ash::STATUS_CLOSED);
EXPECT_EQ(3, model_->item_count());
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[2].type);
EXPECT_EQ(ash::STATUS_CLOSED, model_->items()[2].status);
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(extension1_->id()));
// Unpinning the app should remove its shelf item.
launcher_controller_->UnpinAppWithID(extension1_->id());
EXPECT_EQ(2, model_->item_count());
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(extension1_->id()));
}
// Test the V1 app interaction flow: pin it, run it, unpin it, close it.
TEST_F(ChromeLauncherControllerImplTest, V1AppPinRunUnpinClose) {
InitLauncherController();
// The model should only contain the browser shortcut and app list items.
EXPECT_EQ(2, model_->item_count());
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(extension1_->id()));
// Pinning the app should create a new shelf item.
launcher_controller_->PinAppWithID(extension1_->id());
EXPECT_EQ(3, model_->item_count());
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[2].type);
EXPECT_EQ(ash::STATUS_CLOSED, model_->items()[2].status);
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(extension1_->id()));
// Reporting that the app is running should just update the existing item.
launcher_controller_->SetV1AppStatus(extension1_->id(), ash::STATUS_RUNNING);
EXPECT_EQ(3, model_->item_count());
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[2].type);
EXPECT_EQ(ash::STATUS_RUNNING, model_->items()[2].status);
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(extension1_->id()));
// Unpinning the app should just update the existing item.
launcher_controller_->UnpinAppWithID(extension1_->id());
EXPECT_EQ(3, model_->item_count());
EXPECT_EQ(ash::TYPE_APP, model_->items()[2].type);
EXPECT_EQ(ash::STATUS_RUNNING, model_->items()[2].status);
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(extension1_->id()));
// Reporting that the app is closed should remove its shelf item.
launcher_controller_->SetV1AppStatus(extension1_->id(), ash::STATUS_CLOSED);
EXPECT_EQ(2, model_->item_count());
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(extension1_->id()));
}
// Ensure unpinned V1 app ordering is properly restored after user changes.
TEST_F(ChromeLauncherControllerImplTest, CheckRunningV1AppOrder) {
InitLauncherController();
// The model should only contain the browser shortcut and app list items.
EXPECT_EQ(2, model_->item_count());
// Add a few running applications.
launcher_controller_->SetV1AppStatus(extension1_->id(), ash::STATUS_RUNNING);
launcher_controller_->SetV1AppStatus(extension2_->id(), ash::STATUS_RUNNING);
launcher_controller_->SetV1AppStatus(extension3_->id(), ash::STATUS_RUNNING);
EXPECT_EQ(5, model_->item_count());
// Note that this not only checks the order of applications but also the
// running type.
EXPECT_EQ("AppList, Chrome, app1, app2, app3", GetPinnedAppStatus());
// Remember the current order of applications for the current user.
const AccountId& current_account_id =
multi_user_util::GetAccountIdFromProfile(profile());
RememberUnpinnedRunningApplicationOrder();
// Switch some items and check that restoring a user which was not yet
// remembered changes nothing.
model_->Move(2, 3);
EXPECT_EQ("AppList, Chrome, app2, app1, app3", GetPinnedAppStatus());
const AccountId second_fake_account_id(
AccountId::FromUserEmail("second-fake-user@fake.com"));
RestoreUnpinnedRunningApplicationOrder(second_fake_account_id);
EXPECT_EQ("AppList, Chrome, app2, app1, app3", GetPinnedAppStatus());
// Restoring the stored user should however do the right thing.
RestoreUnpinnedRunningApplicationOrder(current_account_id);
EXPECT_EQ("AppList, Chrome, app1, app2, app3", GetPinnedAppStatus());
// Switch again some items and even delete one - making sure that the missing
// item gets properly handled.
model_->Move(3, 4);
launcher_controller_->SetV1AppStatus(extension1_->id(), ash::STATUS_CLOSED);
EXPECT_EQ("AppList, Chrome, app3, app2", GetPinnedAppStatus());
RestoreUnpinnedRunningApplicationOrder(current_account_id);
EXPECT_EQ("AppList, Chrome, app2, app3", GetPinnedAppStatus());
// Check that removing more items does not crash and changes nothing.
launcher_controller_->SetV1AppStatus(extension2_->id(), ash::STATUS_CLOSED);
RestoreUnpinnedRunningApplicationOrder(current_account_id);
EXPECT_EQ("AppList, Chrome, app3", GetPinnedAppStatus());
launcher_controller_->SetV1AppStatus(extension3_->id(), ash::STATUS_CLOSED);
RestoreUnpinnedRunningApplicationOrder(current_account_id);
EXPECT_EQ("AppList, Chrome", GetPinnedAppStatus());
}
TEST_P(ChromeLauncherControllerImplWithArcTest, ArcDeferredLaunch) {
InitLauncherController();
const arc::mojom::AppInfo& app1 = arc_test_.fake_apps()[0];
const arc::mojom::AppInfo& app2 = arc_test_.fake_apps()[1];
const arc::mojom::AppInfo& app3 = arc_test_.fake_apps()[2];
const arc::mojom::ShortcutInfo& shortcut = arc_test_.fake_shortcuts()[0];
const std::string arc_app_id1 = ArcAppTest::GetAppId(app1);
const std::string arc_app_id2 = ArcAppTest::GetAppId(app2);
const std::string arc_app_id3 = ArcAppTest::GetAppId(app3);
const std::string arc_shortcut_id = ArcAppTest::GetAppId(shortcut);
SendListOfArcApps();
SendListOfArcShortcuts();
arc_test_.StopArcInstance();
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id1));
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id2));
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id3));
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_shortcut_id));
arc::LaunchApp(profile(), arc_app_id1, ui::EF_LEFT_MOUSE_BUTTON);
arc::LaunchApp(profile(), arc_app_id1, ui::EF_LEFT_MOUSE_BUTTON);
arc::LaunchApp(profile(), arc_app_id2, ui::EF_LEFT_MOUSE_BUTTON);
arc::LaunchApp(profile(), arc_app_id3, ui::EF_LEFT_MOUSE_BUTTON);
arc::LaunchApp(profile(), arc_shortcut_id, ui::EF_LEFT_MOUSE_BUTTON);
const ash::ShelfID shelf_id_app_1 =
launcher_controller_->GetShelfIDForAppID(arc_app_id1);
const ash::ShelfID shelf_id_app_2 =
launcher_controller_->GetShelfIDForAppID(arc_app_id2);
const ash::ShelfID shelf_id_app_3 =
launcher_controller_->GetShelfIDForAppID(arc_app_id3);
const ash::ShelfID shelf_id_shortcut =
launcher_controller_->GetShelfIDForAppID(arc_shortcut_id);
EXPECT_NE(ash::kInvalidShelfID, shelf_id_app_1);
EXPECT_NE(ash::kInvalidShelfID, shelf_id_app_2);
EXPECT_NE(ash::kInvalidShelfID, shelf_id_app_3);
EXPECT_NE(ash::kInvalidShelfID, shelf_id_shortcut);
// We activated arc_app_id1 twice but expect one close for item controller
// stops launching request.
ash::ShelfItemDelegate* item_delegate =
model_->GetShelfItemDelegate(shelf_id_app_1);
ASSERT_NE(nullptr, item_delegate);
item_delegate->Close();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id1));
EXPECT_EQ(shelf_id_app_2,
launcher_controller_->GetShelfIDForAppID(arc_app_id2));
EXPECT_EQ(shelf_id_app_3,
launcher_controller_->GetShelfIDForAppID(arc_app_id3));
arc_test_.RestartArcInstance();
SendListOfArcApps();
base::RunLoop().RunUntilIdle();
// Now deferred contollers should go away together with shelf items and ARC
// app instance should receive request for launching apps and shortcuts.
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id1));
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id2));
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id3));
ASSERT_EQ(2U, arc_test_.app_instance()->launch_requests().size());
ASSERT_EQ(1U, arc_test_.app_instance()->launch_intents().size());
const arc::FakeAppInstance::Request* request1 =
arc_test_.app_instance()->launch_requests()[0].get();
const arc::FakeAppInstance::Request* request2 =
arc_test_.app_instance()->launch_requests()[1].get();
EXPECT_TRUE((request1->IsForApp(app2) && request2->IsForApp(app3)) ||
(request1->IsForApp(app3) && request2->IsForApp(app2)));
EXPECT_EQ(arc_test_.app_instance()->launch_intents()[0].c_str(),
shortcut.intent_uri);
}
// Ensure the deferred controller does not override the active app controller
// (crbug.com/701152).
TEST_P(ChromeLauncherControllerImplWithArcTest, ArcDeferredLaunchForActiveApp) {
InitLauncherController();
SendListOfArcApps();
arc_test_.StopArcInstance();
const arc::mojom::AppInfo& app = arc_test_.fake_apps()[0];
const std::string app_id = ArcAppTest::GetAppId(app);
launcher_controller_->PinAppWithID(app_id);
EXPECT_TRUE(launcher_controller_->IsAppPinned(app_id));
const ash::ShelfID shelf_id =
launcher_controller_->GetShelfIDForAppID(app_id);
EXPECT_NE(ash::kInvalidShelfID, shelf_id);
int item_index = model_->ItemIndexByID(shelf_id);
ASSERT_GE(item_index, 0);
EXPECT_EQ(model_->items()[item_index].status, ash::STATUS_CLOSED);
EXPECT_EQ(model_->items()[item_index].type, ash::TYPE_PINNED_APP);
// Play Store app is ARC app that might be represented by native Chrome
// platform app.
model_->SetShelfItemDelegate(
shelf_id, base::MakeUnique<ExtensionAppWindowLauncherItemController>(
ash::AppLaunchId(app_id)));
launcher_controller_->SetItemStatus(shelf_id, ash::STATUS_RUNNING);
// This launch request should be ignored in case of active app.
arc::LaunchApp(profile(), app_id, ui::EF_LEFT_MOUSE_BUTTON);
EXPECT_FALSE(launcher_controller_->GetArcDeferredLauncher()->HasApp(app_id));
// Close app but shortcut should exist.
launcher_controller_->CloseLauncherItem(shelf_id);
EXPECT_EQ(shelf_id, launcher_controller_->GetShelfIDForAppID(app_id));
// This should switch shelf item into closed state.
item_index = model_->ItemIndexByID(shelf_id);
ASSERT_GE(item_index, 0);
EXPECT_EQ(model_->items()[item_index].status, ash::STATUS_CLOSED);
EXPECT_EQ(model_->items()[item_index].type, ash::TYPE_PINNED_APP);
// Now launch request should not be ignored.
arc::LaunchApp(profile(), app_id, ui::EF_LEFT_MOUSE_BUTTON);
EXPECT_TRUE(launcher_controller_->GetArcDeferredLauncher()->HasApp(app_id));
}
TEST_P(ChromeLauncherControllerImplMultiProfileWithArcTest, ArcMultiUser) {
SendListOfArcApps();
InitLauncherController();
// TODO(crbug.com/654622): This test breaks with a non-null static instance.
ChromeLauncherControllerImpl::set_instance_for_test(nullptr);
SetLauncherControllerHelper(new TestLauncherControllerHelper);
// App1 exists all the time.
// App2 is created when primary user is active and destroyed when secondary
// user is active.
// App3 created when secondary user is active.
const std::string user2 = "user2";
TestingProfile* profile2 = CreateMultiUserProfile(user2);
const AccountId account_id(
multi_user_util::GetAccountIdFromProfile(profile()));
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
const std::string arc_app_id1 =
ArcAppTest::GetAppId(arc_test_.fake_apps()[0]);
const std::string arc_app_id2 =
ArcAppTest::GetAppId(arc_test_.fake_apps()[1]);
const std::string arc_app_id3 =
ArcAppTest::GetAppId(arc_test_.fake_apps()[2]);
std::string window_app_id1("org.chromium.arc.1");
views::Widget* arc_window1 = CreateArcWindow(window_app_id1);
arc_test_.app_instance()->SendTaskCreated(1, arc_test_.fake_apps()[0],
std::string());
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id1));
std::string window_app_id2("org.chromium.arc.2");
views::Widget* arc_window2 = CreateArcWindow(window_app_id2);
arc_test_.app_instance()->SendTaskCreated(2, arc_test_.fake_apps()[1],
std::string());
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id2));
launcher_controller_->SetProfileForTest(profile2);
SwitchActiveUser(account_id2);
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id1));
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id2));
std::string window_app_id3("org.chromium.arc.3");
views::Widget* arc_window3 = CreateArcWindow(window_app_id3);
arc_test_.app_instance()->SendTaskCreated(3, arc_test_.fake_apps()[2],
std::string());
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id3));
arc_window2->CloseNow();
arc_test_.app_instance()->SendTaskDestroyed(2);
launcher_controller_->SetProfileForTest(profile());
SwitchActiveUser(account_id);
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id1));
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id2));
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id3));
// Close active window to let test passes.
arc_window1->CloseNow();
arc_window3->CloseNow();
}
TEST_P(ChromeLauncherControllerImplWithArcTest, ArcRunningApp) {
InitLauncherController();
const std::string arc_app_id = ArcAppTest::GetAppId(arc_test_.fake_apps()[0]);
SendListOfArcApps();
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id));
// Normal flow, create/destroy tasks.
std::string window_app_id1("org.chromium.arc.1");
std::string window_app_id2("org.chromium.arc.2");
std::string window_app_id3("org.chromium.arc.3");
CreateArcWindow(window_app_id1);
arc_test_.app_instance()->SendTaskCreated(1, arc_test_.fake_apps()[0],
std::string());
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id));
CreateArcWindow(window_app_id2);
arc_test_.app_instance()->SendTaskCreated(2, arc_test_.fake_apps()[0],
std::string());
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id));
arc_test_.app_instance()->SendTaskDestroyed(1);
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id));
arc_test_.app_instance()->SendTaskDestroyed(2);
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id));
// Stopping bridge removes apps.
CreateArcWindow(window_app_id3);
arc_test_.app_instance()->SendTaskCreated(3, arc_test_.fake_apps()[0],
std::string());
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id));
arc_test_.StopArcInstance();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id));
}
// Test race creation/deletion of ARC app.
// TODO(khmel): Remove after moving everything to wayland protocol.
TEST_P(ChromeLauncherControllerImplWithArcTest, ArcRaceCreateClose) {
InitLauncherController();
const std::string arc_app_id1 =
ArcAppTest::GetAppId(arc_test_.fake_apps()[0]);
const std::string arc_app_id2 =
ArcAppTest::GetAppId(arc_test_.fake_apps()[1]);
SendListOfArcApps();
// ARC window created before and closed after mojom notification.
std::string window_app_id1("org.chromium.arc.1");
views::Widget* arc_window = CreateArcWindow(window_app_id1);
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id1));
ASSERT_TRUE(arc_window);
arc_test_.app_instance()->SendTaskCreated(1, arc_test_.fake_apps()[0],
std::string());
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id1));
arc_test_.app_instance()->SendTaskDestroyed(1);
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id1));
arc_window->Close();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id1));
// ARC window created after and closed before mojom notification.
std::string window_app_id2("org.chromium.arc.2");
arc_test_.app_instance()->SendTaskCreated(2, arc_test_.fake_apps()[1],
std::string());
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id2));
arc_window = CreateArcWindow(window_app_id2);
ASSERT_TRUE(arc_window);
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id2));
arc_window->Close();
base::RunLoop().RunUntilIdle();
// Closing window does not close shelf item. It is closed on task destroy.
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id2));
arc_test_.app_instance()->SendTaskDestroyed(2);
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(arc_app_id2));
}
TEST_P(ChromeLauncherControllerImplWithArcTest, ArcWindowRecreation) {
InitLauncherController();
const std::string arc_app_id = ArcAppTest::GetAppId(arc_test_.fake_apps()[0]);
SendListOfArcApps();
std::string window_app_id("org.chromium.arc.1");
views::Widget* arc_window = CreateArcWindow(window_app_id);
ASSERT_TRUE(arc_window);
arc_test_.app_instance()->SendTaskCreated(1, arc_test_.fake_apps()[0],
std::string());
const ash::ShelfID shelf_id =
launcher_controller_->GetShelfIDForAppID(arc_app_id);
EXPECT_NE(ash::kInvalidShelfID, shelf_id);
for (int i = 0; i < 3; ++i) {
arc_window->Close();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(shelf_id, launcher_controller_->GetShelfIDForAppID(arc_app_id));
arc_window = CreateArcWindow(window_app_id);
ASSERT_TRUE(arc_window);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(shelf_id, launcher_controller_->GetShelfIDForAppID(arc_app_id));
}
}
// Validate that ARC app is pinned correctly and pin is removed automatically
// once app is uninstalled.
TEST_P(ChromeLauncherControllerImplWithArcTest, ArcAppPin) {
InitLauncherController();
const std::string arc_app_id = ArcAppTest::GetAppId(arc_test_.fake_apps()[0]);
SendListOfArcApps();
extension_service_->AddExtension(extension1_.get());
extension_service_->AddExtension(extension2_.get());
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(arc_app_id));
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension2_->id()));
launcher_controller_->PinAppWithID(extension1_->id());
launcher_controller_->PinAppWithID(arc_app_id);
launcher_controller_->PinAppWithID(extension2_->id());
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_TRUE(launcher_controller_->IsAppPinned(arc_app_id));
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension2_->id()));
EXPECT_EQ("AppList, Chrome, App1, Fake App 0, App2", GetPinnedAppStatus());
// In opt-out mode, only system apps are available and can't be uninstalled.
// Skip the rest of the test.
if (arc::ShouldArcAlwaysStart())
return;
UninstallArcApps();
EXPECT_FALSE(launcher_controller_->IsAppPinned(arc_app_id));
EXPECT_EQ("AppList, Chrome, App1, App2", GetPinnedAppStatus());
SendListOfArcApps();
EXPECT_FALSE(launcher_controller_->IsAppPinned(arc_app_id));
EXPECT_EQ("AppList, Chrome, App1, App2", GetPinnedAppStatus());
// Opt-Out/Opt-In remove item from the shelf.
launcher_controller_->PinAppWithID(arc_app_id);
EXPECT_EQ("AppList, Chrome, App1, App2, Fake App 0", GetPinnedAppStatus());
EnablePlayStore(false);
EXPECT_EQ("AppList, Chrome, App1, App2", GetPinnedAppStatus());
EnablePlayStore(true);
EXPECT_EQ("AppList, Chrome, App1, App2", GetPinnedAppStatus());
SendListOfArcApps();
EXPECT_EQ("AppList, Chrome, App1, App2, Fake App 0", GetPinnedAppStatus());
}
// Validates that ARC app pins persist across OptOut/OptIn.
TEST_P(ChromeLauncherControllerImplWithArcTest, ArcAppPinOptOutOptIn) {
InitLauncherController();
const std::string arc_app_id1 =
ArcAppTest::GetAppId(arc_test_.fake_apps()[0]);
const std::string arc_app_id2 =
ArcAppTest::GetAppId(arc_test_.fake_apps()[1]);
SendListOfArcApps();
extension_service_->AddExtension(extension1_.get());
extension_service_->AddExtension(extension2_.get());
launcher_controller_->PinAppWithID(extension1_->id());
launcher_controller_->PinAppWithID(arc_app_id2);
launcher_controller_->PinAppWithID(extension2_->id());
launcher_controller_->PinAppWithID(arc_app_id1);
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_TRUE(launcher_controller_->IsAppPinned(arc_app_id1));
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension2_->id()));
EXPECT_TRUE(launcher_controller_->IsAppPinned(arc_app_id2));
EXPECT_EQ("AppList, Chrome, App1, Fake App 1, App2, Fake App 0",
GetPinnedAppStatus());
// TODO(victorhsieh): Implement opt-out.
if (arc::ShouldArcAlwaysStart())
return;
EnablePlayStore(false);
EXPECT_EQ("AppList, Chrome, App1, App2", GetPinnedAppStatus());
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(arc_app_id1));
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension2_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(arc_app_id2));
EnablePlayStore(true);
SendListOfArcApps();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_TRUE(launcher_controller_->IsAppPinned(arc_app_id1));
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension2_->id()));
EXPECT_TRUE(launcher_controller_->IsAppPinned(arc_app_id2));
EXPECT_EQ("AppList, Chrome, App1, Fake App 1, App2, Fake App 0",
GetPinnedAppStatus());
}
// Check that with multi profile V1 apps are properly added / removed from the
// shelf.
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeLauncherControllerImplTest,
V1AppUpdateOnUserSwitch) {
// Create a browser item in the LauncherController.
InitLauncherController();
EXPECT_EQ(2, model_->item_count());
{
// Create a "windowed gmail app".
std::unique_ptr<V1App> v1_app(
CreateRunningV1App(profile(), extension_misc::kGmailAppId, gmail_url));
EXPECT_EQ(3, model_->item_count());
// After switching to a second user the item should be gone.
std::string user2 = "user2";
TestingProfile* profile2 = CreateMultiUserProfile(user2);
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
const AccountId account_id(
multi_user_util::GetAccountIdFromProfile(profile()));
SwitchActiveUser(account_id2);
EXPECT_EQ(2, model_->item_count());
// After switching back the item should be back.
SwitchActiveUser(account_id);
EXPECT_EQ(3, model_->item_count());
// Note we destroy now the gmail app with the closure end.
}
EXPECT_EQ(2, model_->item_count());
}
// Check edge cases with multi profile V1 apps in the shelf.
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeLauncherControllerImplTest,
V1AppUpdateOnUserSwitchEdgecases) {
// Create a browser item in the LauncherController.
InitLauncherController();
// First test: Create an app when the user is not active.
std::string user2 = "user2";
TestingProfile* profile2 = CreateMultiUserProfile(user2);
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
const AccountId account_id(
multi_user_util::GetAccountIdFromProfile(profile()));
{
// Create a "windowed gmail app".
std::unique_ptr<V1App> v1_app(
CreateRunningV1App(profile2, extension_misc::kGmailAppId, gmail_url));
EXPECT_EQ(2, model_->item_count());
// However - switching to the user should show it.
SwitchActiveUser(account_id2);
EXPECT_EQ(3, model_->item_count());
// Second test: Remove the app when the user is not active and see that it
// works.
SwitchActiveUser(account_id);
EXPECT_EQ(2, model_->item_count());
// Note: the closure ends and the browser will go away.
}
EXPECT_EQ(2, model_->item_count());
SwitchActiveUser(account_id2);
EXPECT_EQ(2, model_->item_count());
SwitchActiveUser(account_id);
EXPECT_EQ(2, model_->item_count());
}
// Check edge case where a visiting V1 app gets closed (crbug.com/321374).
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeLauncherControllerImplTest,
V1CloseOnVisitingDesktop) {
// Create a browser item in the LauncherController.
InitLauncherController();
chrome::MultiUserWindowManager* manager =
chrome::MultiUserWindowManager::GetInstance();
// First create an app when the user is active.
std::string user2 = "user2";
TestingProfile* profile2 = CreateMultiUserProfile(user2);
const AccountId account_id(
multi_user_util::GetAccountIdFromProfile(profile()));
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
{
// Create a "windowed gmail app".
std::unique_ptr<V1App> v1_app(CreateRunningV1App(
profile(), extension_misc::kGmailAppId, kGmailLaunchURL));
EXPECT_EQ(3, model_->item_count());
// Transfer the app to the other screen and switch users.
manager->ShowWindowForUser(v1_app->browser()->window()->GetNativeWindow(),
account_id2);
EXPECT_EQ(3, model_->item_count());
SwitchActiveUser(account_id2);
EXPECT_EQ(2, model_->item_count());
}
// After the app was destroyed, switch back. (which caused already a crash).
SwitchActiveUser(account_id);
// Create the same app again - which was also causing the crash.
EXPECT_EQ(2, model_->item_count());
{
// Create a "windowed gmail app".
std::unique_ptr<V1App> v1_app(CreateRunningV1App(
profile(), extension_misc::kGmailAppId, kGmailLaunchURL));
EXPECT_EQ(3, model_->item_count());
}
SwitchActiveUser(account_id2);
EXPECT_EQ(2, model_->item_count());
}
// Check edge cases with multi profile V1 apps in the shelf.
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeLauncherControllerImplTest,
V1AppUpdateOnUserSwitchEdgecases2) {
// Create a browser item in the LauncherController.
InitLauncherController();
// First test: Create an app when the user is not active.
std::string user2 = "user2";
TestingProfile* profile2 = CreateMultiUserProfile(user2);
const AccountId account_id(
multi_user_util::GetAccountIdFromProfile(profile()));
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
SwitchActiveUser(account_id2);
{
// Create a "windowed gmail app".
std::unique_ptr<V1App> v1_app(
CreateRunningV1App(profile(), extension_misc::kGmailAppId, gmail_url));
EXPECT_EQ(2, model_->item_count());
// However - switching to the user should show it.
SwitchActiveUser(account_id);
EXPECT_EQ(3, model_->item_count());
// Second test: Remove the app when the user is not active and see that it
// works.
SwitchActiveUser(account_id2);
EXPECT_EQ(2, model_->item_count());
v1_app.reset();
}
EXPECT_EQ(2, model_->item_count());
SwitchActiveUser(account_id);
EXPECT_EQ(2, model_->item_count());
SwitchActiveUser(account_id2);
EXPECT_EQ(2, model_->item_count());
}
// Check that activating an item which is on another user's desktop, will bring
// it back.
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeLauncherControllerImplTest,
TestLauncherActivationPullsBackWindow) {
// Create a browser item in the LauncherController.
InitLauncherController();
chrome::MultiUserWindowManager* manager =
chrome::MultiUserWindowManager::GetInstance();
// Create a second test profile. The first is the one in profile() created in
// BrowserWithTestWindowTest::SetUp().
// No need to add the profiles to the MultiUserWindowManager here.
// CreateMultiUserProfile() already does that.
TestingProfile* profile2 = CreateMultiUserProfile("user2");
const AccountId current_user =
multi_user_util::GetAccountIdFromProfile(profile());
// Create a browser window with a native window for the current user.
std::unique_ptr<Browser> browser(
CreateBrowserWithTestWindowForProfile(profile()));
BrowserWindow* browser_window = browser->window();
aura::Window* window = browser_window->GetNativeWindow();
manager->SetWindowOwner(window, current_user);
// Check that an activation of the window on its owner's desktop does not
// change the visibility to another user.
launcher_controller_->ActivateWindowOrMinimizeIfActive(browser_window, false);
EXPECT_TRUE(manager->IsWindowOnDesktopOfUser(window, current_user));
// Transfer the window to another user's desktop and check that activating it
// does pull it back to that user.
manager->ShowWindowForUser(
window, multi_user_util::GetAccountIdFromProfile(profile2));
EXPECT_FALSE(manager->IsWindowOnDesktopOfUser(window, current_user));
launcher_controller_->ActivateWindowOrMinimizeIfActive(browser_window, false);
EXPECT_TRUE(manager->IsWindowOnDesktopOfUser(window, current_user));
}
// Check that a running windowed V1 application will be properly pinned and
// unpinned when the order gets changed through a profile / policy change.
TEST_F(ChromeLauncherControllerImplTest,
RestoreDefaultAndRunningV1AppsResyncOrder) {
InitLauncherController();
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 0, extension1_->id());
InsertAddPinChange(&sync_list, 1, extension3_->id());
SendPinChanges(sync_list, true);
// The shelf layout has always one static item at the beginning (App List).
extension_service_->AddExtension(extension1_.get());
EXPECT_EQ("AppList, Chrome, App1", GetPinnedAppStatus());
extension_service_->AddExtension(extension2_.get());
// No new app icon will be generated.
EXPECT_EQ("AppList, Chrome, App1", GetPinnedAppStatus());
// Set the app status as running, which will add an unpinned item.
launcher_controller_->SetV1AppStatus(extension2_->id(), ash::STATUS_RUNNING);
EXPECT_EQ("AppList, Chrome, App1, app2", GetPinnedAppStatus());
extension_service_->AddExtension(extension3_.get());
EXPECT_EQ("AppList, Chrome, App1, App3, app2", GetPinnedAppStatus());
// Now request to pin all items, which will pin the running unpinned items.
syncer::SyncChangeList sync_list1;
InsertAddPinChange(&sync_list1, 0, extension3_->id());
InsertAddPinChange(&sync_list1, 1, extension2_->id());
InsertAddPinChange(&sync_list1, 2, extension1_->id());
SendPinChanges(sync_list1, true);
EXPECT_EQ("AppList, Chrome, App3, App2, App1", GetPinnedAppStatus());
// Removing the requirement for app 2 to be pinned should convert it back to
// running but not pinned. It should move towards the end of the shelf, after
// the pinned items, as determined by the |ShelfModel|'s weight system.
syncer::SyncChangeList sync_list2;
InsertAddPinChange(&sync_list2, 0, extension3_->id());
InsertAddPinChange(&sync_list2, 1, extension1_->id());
SendPinChanges(sync_list2, true);
EXPECT_EQ("AppList, Chrome, App3, App1, app2", GetPinnedAppStatus());
// Removing an item should simply close it and everything should shift.
SendPinChanges(syncer::SyncChangeList(), true);
EXPECT_EQ("AppList, Chrome, App3, app2", GetPinnedAppStatus());
}
// Check that a running unpinned V2 application will be properly pinned and
// unpinned when the order gets changed through a profile / policy change.
TEST_F(ChromeLauncherControllerImplTest,
RestoreDefaultAndRunningV2AppsResyncOrder) {
InitLauncherController();
syncer::SyncChangeList sync_list0;
InsertAddPinChange(&sync_list0, 0, extension1_->id());
InsertAddPinChange(&sync_list0, 1, extension3_->id());
SendPinChanges(sync_list0, true);
// The shelf layout has always one static item at the beginning (app List).
extension_service_->AddExtension(extension1_.get());
EXPECT_EQ("AppList, Chrome, App1", GetPinnedAppStatus());
extension_service_->AddExtension(extension_platform_app_.get());
// No new app icon will be generated.
EXPECT_EQ("AppList, Chrome, App1", GetPinnedAppStatus());
// Add an unpinned but running V2 app.
CreateRunningV2App(extension_platform_app_->id());
EXPECT_EQ("AppList, Chrome, App1, *platform_app", GetPinnedAppStatus());
extension_service_->AddExtension(extension3_.get());
EXPECT_EQ("AppList, Chrome, App1, App3, *platform_app", GetPinnedAppStatus());
// Now request to pin all items, which should pin the running unpinned item.
syncer::SyncChangeList sync_list1;
InsertAddPinChange(&sync_list1, 0, extension3_->id());
InsertAddPinChange(&sync_list1, 1, extension_platform_app_->id());
InsertAddPinChange(&sync_list1, 2, extension1_->id());
SendPinChanges(sync_list1, true);
EXPECT_EQ("AppList, Chrome, App3, *Platform_App, App1", GetPinnedAppStatus());
// Removing the requirement for app 2 to be pinned should convert it back to
// running but not pinned. It should move towards the end of the shelf, after
// the pinned items, as determined by the |ShelfModel|'s weight system.
syncer::SyncChangeList sync_list2;
InsertAddPinChange(&sync_list2, 0, extension3_->id());
InsertAddPinChange(&sync_list2, 1, extension1_->id());
SendPinChanges(sync_list2, true);
EXPECT_EQ("AppList, Chrome, App3, App1, *platform_app", GetPinnedAppStatus());
// Removing an item should simply close it and everything should shift.
syncer::SyncChangeList sync_list3;
InsertAddPinChange(&sync_list3, 0, extension3_->id());
SendPinChanges(sync_list3, true);
EXPECT_EQ("AppList, Chrome, App3, *platform_app", GetPinnedAppStatus());
}
// Each user has a different set of applications pinned. Check that when
// switching between the two users, the state gets properly set.
TEST_F(ChromeLauncherControllerImplTest, UserSwitchIconRestore) {
syncer::SyncChangeList user_a;
syncer::SyncChangeList user_b;
SetUpMultiUserScenario(&user_a, &user_b);
// Show user 1.
SendPinChanges(user_a, true);
EXPECT_EQ("AppList, App1, App2, App3, *Platform_App, App4, App5, Chrome",
GetPinnedAppStatus());
// Show user 2.
SendPinChanges(user_b, true);
EXPECT_EQ("AppList, App6, App7, App8, Chrome", GetPinnedAppStatus());
// Switch back to 1.
SendPinChanges(user_a, true);
EXPECT_EQ("AppList, App1, App2, App3, *Platform_App, App4, App5, Chrome",
GetPinnedAppStatus());
// Switch back to 2.
SendPinChanges(user_b, true);
EXPECT_EQ("AppList, App6, App7, App8, Chrome", GetPinnedAppStatus());
}
// Each user has a different set of applications pinned, and one user has an
// application running. Check that when switching between the two users, the
// state gets properly set.
TEST_F(ChromeLauncherControllerImplTest,
UserSwitchIconRestoreWithRunningV2App) {
syncer::SyncChangeList user_a;
syncer::SyncChangeList user_b;
SetUpMultiUserScenario(&user_a, &user_b);
// Run the platform (V2) app.
CreateRunningV2App(extension_platform_app_->id());
// Show user 1.
SendPinChanges(user_a, true);
EXPECT_EQ("AppList, App1, App2, App3, *Platform_App, App4, App5, Chrome",
GetPinnedAppStatus());
// Show user 2.
SendPinChanges(user_b, true);
EXPECT_EQ("AppList, App6, App7, App8, Chrome, *platform_app",
GetPinnedAppStatus());
// Switch back to 1.
SendPinChanges(user_a, true);
EXPECT_EQ("AppList, App1, App2, App3, *Platform_App, App4, App5, Chrome",
GetPinnedAppStatus());
// Switch back to 2.
SendPinChanges(user_b, true);
EXPECT_EQ("AppList, App6, App7, App8, Chrome, *platform_app",
GetPinnedAppStatus());
}
// Each user has a different set of applications pinned, and one user has an
// application running. The chrome icon is not the last item in the list.
// Check that when switching between the two users, the state gets properly set.
// There was once a bug associated with this.
TEST_F(ChromeLauncherControllerImplTest,
UserSwitchIconRestoreWithRunningV2AppChromeInMiddle) {
syncer::SyncChangeList user_a;
syncer::SyncChangeList user_b;
SetUpMultiUserScenario(&user_a, &user_b);
// Run the platform (V2) app.
CreateRunningV2App(extension_platform_app_->id());
// Show user 1.
SendPinChanges(user_a, true);
SetShelfChromeIconIndex(5);
EXPECT_EQ("AppList, App1, App2, App3, *Platform_App, App4, Chrome, App5",
GetPinnedAppStatus());
// Show user 2.
SendPinChanges(user_b, true);
SetShelfChromeIconIndex(4);
EXPECT_EQ("AppList, App6, App7, App8, Chrome, *platform_app",
GetPinnedAppStatus());
// Switch back to 1.
SendPinChanges(user_a, true);
SetShelfChromeIconIndex(5);
EXPECT_EQ("AppList, App1, App2, App3, *Platform_App, App4, Chrome, App5",
GetPinnedAppStatus());
}
TEST_F(ChromeLauncherControllerImplTest, Policy) {
extension_service_->AddExtension(extension1_.get());
extension_service_->AddExtension(extension3_.get());
InitLauncherController();
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 0, extension_misc::kChromeAppId);
SendPinChanges(sync_list, true);
base::ListValue policy_value;
InsertPrefValue(&policy_value, 0, extension1_->id());
InsertPrefValue(&policy_value, 1, extension2_->id());
profile()->GetTestingPrefService()->SetManagedPref(
prefs::kPolicyPinnedLauncherApps, policy_value.CreateDeepCopy());
// Only |extension1_| should get pinned. |extension2_| is specified but not
// installed, and |extension3_| is part of the default set, but that shouldn't
// take effect when the policy override is in place.
ASSERT_EQ(3, model_->item_count());
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[1].type);
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension2_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension3_->id()));
// Installing |extension2_| should add it to the launcher.
extension_service_->AddExtension(extension2_.get());
ASSERT_EQ(4, model_->item_count());
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[1].type);
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[2].type);
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension2_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension3_->id()));
// Removing |extension1_| from the policy should not be reflected in the
// launcher and pin will exist.
policy_value.Remove(0, NULL);
profile()->GetTestingPrefService()->SetManagedPref(
prefs::kPolicyPinnedLauncherApps, policy_value.CreateDeepCopy());
EXPECT_EQ(4, model_->item_count());
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[2].type);
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension2_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension3_->id()));
}
TEST_F(ChromeLauncherControllerImplTest, UnpinWithUninstall) {
extension_service_->AddExtension(extension3_.get());
extension_service_->AddExtension(extension4_.get());
InitLauncherController();
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension3_->id()));
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension4_->id()));
extension_service_->UnloadExtension(extension3_->id(),
UnloadedExtensionInfo::REASON_UNINSTALL);
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension3_->id()));
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension4_->id()));
}
TEST_F(ChromeLauncherControllerImplTest, SyncUpdates) {
extension_service_->AddExtension(extension2_.get());
extension_service_->AddExtension(extension3_.get());
extension_service_->AddExtension(extension4_.get());
InitLauncherController();
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 10, extension_misc::kChromeAppId);
SendPinChanges(sync_list, true);
std::vector<std::string> expected_pinned_apps;
std::vector<std::string> actual_pinned_apps;
GetPinnedAppIds(launcher_controller_, &actual_pinned_apps);
EXPECT_EQ(expected_pinned_apps, actual_pinned_apps);
// Unavailable extensions don't create launcher items.
sync_list.clear();
InsertAddPinChange(&sync_list, 0, extension1_->id());
InsertAddPinChange(&sync_list, 1, extension2_->id());
InsertAddPinChange(&sync_list, 3, extension4_->id());
SendPinChanges(sync_list, false);
expected_pinned_apps.push_back(extension2_->id());
expected_pinned_apps.push_back(extension4_->id());
GetPinnedAppIds(launcher_controller_, &actual_pinned_apps);
EXPECT_EQ(expected_pinned_apps, actual_pinned_apps);
sync_list.clear();
InsertAddPinChange(&sync_list, 2, extension3_->id());
SendPinChanges(sync_list, false);
expected_pinned_apps.insert(expected_pinned_apps.begin() + 1,
extension3_->id());
GetPinnedAppIds(launcher_controller_, &actual_pinned_apps);
EXPECT_EQ(expected_pinned_apps, actual_pinned_apps);
sync_list.clear();
InsertUpdatePinChange(&sync_list, 0, extension4_->id());
InsertUpdatePinChange(&sync_list, 1, extension3_->id());
InsertUpdatePinChange(&sync_list, 2, extension2_->id());
SendPinChanges(sync_list, false);
std::reverse(expected_pinned_apps.begin(), expected_pinned_apps.end());
GetPinnedAppIds(launcher_controller_, &actual_pinned_apps);
EXPECT_EQ(expected_pinned_apps, actual_pinned_apps);
// Sending legacy sync change without pin info should not affect pin model.
sync_list.clear();
InsertLegacyPinChange(&sync_list, extension4_->id());
SendPinChanges(sync_list, false);
GetPinnedAppIds(launcher_controller_, &actual_pinned_apps);
EXPECT_EQ(expected_pinned_apps, actual_pinned_apps);
sync_list.clear();
InsertRemovePinChange(&sync_list, extension4_->id());
SendPinChanges(sync_list, false);
expected_pinned_apps.erase(expected_pinned_apps.begin());
GetPinnedAppIds(launcher_controller_, &actual_pinned_apps);
EXPECT_EQ(expected_pinned_apps, actual_pinned_apps);
sync_list.clear();
InsertRemovePinChange(&sync_list, extension3_->id());
InsertRemovePinChange(&sync_list, extension2_->id());
SendPinChanges(sync_list, false);
expected_pinned_apps.clear();
GetPinnedAppIds(launcher_controller_, &actual_pinned_apps);
EXPECT_EQ(expected_pinned_apps, actual_pinned_apps);
}
TEST_F(ChromeLauncherControllerImplTest, ImportLegacyPin) {
// Note extension3_ is actually Gmail app which is default pinned.
extension_service_->AddExtension(extension3_.get());
InitLauncherController();
// Default pins should contain Gmail. Pref is not syncing now.
EXPECT_EQ("AppList, Chrome, App3", GetPinnedAppStatus());
extension_service_->AddExtension(extension2_.get());
EXPECT_EQ("AppList, Chrome, App3", GetPinnedAppStatus());
// Initially pins are imported from legacy pref based model.
base::ListValue value;
InsertPrefValue(&value, 0, extension4_->id());
InsertPrefValue(&value, 1, extension2_->id());
InsertPrefValue(&value, 2, extension3_->id());
StartPrefSyncServiceForPins(value);
// Imported pins contain App2. App2 should be added to pins now.
EXPECT_EQ("AppList, Chrome, App2, App3", GetPinnedAppStatus());
// extension4_ is in the pin list.
extension_service_->AddExtension(extension4_.get());
// extension5_ is not in the pin list.
extension_service_->AddExtension(extension5_.get());
EXPECT_EQ("AppList, Chrome, App4, App2, App3", GetPinnedAppStatus());
// Apply app sync, unpin one app and pin new one.
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, -1, extension3_->id());
InsertAddPinChange(&sync_list, 3, extension5_->id());
SendPinChanges(sync_list, false);
EXPECT_EQ("AppList, Chrome, App4, App2, App5", GetPinnedAppStatus());
// At this point changing old pref based model does not affect pin model.
InsertPrefValue(&value, 3, extension5_->id());
StopPrefSyncService();
StartPrefSyncServiceForPins(value);
EXPECT_EQ("AppList, Chrome, App4, App2, App5", GetPinnedAppStatus());
// Next Chrome start should preserve pins.
RecreateLauncherController()->Init();
StopPrefSyncService();
StartPrefSyncService(syncer::SyncDataList());
EXPECT_EQ("AppList, Chrome, App4, App2, App5", GetPinnedAppStatus());
}
TEST_F(ChromeLauncherControllerImplTest, PendingInsertionOrder) {
extension_service_->AddExtension(extension1_.get());
extension_service_->AddExtension(extension3_.get());
InitLauncherController();
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 0, extension1_->id());
InsertAddPinChange(&sync_list, 1, extension2_->id());
InsertAddPinChange(&sync_list, 2, extension3_->id());
SendPinChanges(sync_list, true);
std::vector<std::string> expected_pinned_apps;
expected_pinned_apps.push_back(extension1_->id());
expected_pinned_apps.push_back(extension3_->id());
std::vector<std::string> actual_pinned_apps;
GetPinnedAppIds(launcher_controller_, &actual_pinned_apps);
EXPECT_EQ(expected_pinned_apps, actual_pinned_apps);
// Install |extension2| and verify it shows up between the other two.
extension_service_->AddExtension(extension2_.get());
expected_pinned_apps.insert(expected_pinned_apps.begin() + 1,
extension2_->id());
GetPinnedAppIds(launcher_controller_, &actual_pinned_apps);
EXPECT_EQ(expected_pinned_apps, actual_pinned_apps);
}
// Ensure |controller| creates the expected menu items for the given shelf item.
void CheckAppMenu(ChromeLauncherControllerImpl* controller,
const ash::ShelfItem& item,
size_t expected_item_count,
base::string16 expected_item_titles[]) {
ash::MenuItemList items = controller->GetAppMenuItemsForTesting(item);
ASSERT_EQ(expected_item_count, items.size());
for (size_t i = 0; i < expected_item_count; i++)
EXPECT_EQ(expected_item_titles[i], items[i]->label);
}
// Check that browsers get reflected correctly in the launcher menu.
TEST_F(ChromeLauncherControllerImplTest, BrowserMenuGeneration) {
EXPECT_EQ(1U, chrome::GetTotalBrowserCount());
chrome::NewTab(browser());
InitLauncherController();
// Check that the browser list is empty at this time.
ash::ShelfItem item_browser;
item_browser.type = ash::TYPE_BROWSER_SHORTCUT;
item_browser.id =
launcher_controller_->GetShelfIDForAppID(extension_misc::kChromeAppId);
CheckAppMenu(launcher_controller_, item_browser, 0, nullptr);
// Now make the created browser() visible by showing its browser window.
browser()->window()->Show();
base::string16 title1 = ASCIIToUTF16("Test1");
NavigateAndCommitActiveTabWithTitle(browser(), GURL("http://test1"), title1);
base::string16 one_menu_item[] = { title1 };
CheckAppMenu(launcher_controller_, item_browser, 1, one_menu_item);
// Create one more browser/window and check that one more was added.
std::unique_ptr<Browser> browser2(
CreateBrowserWithTestWindowForProfile(profile()));
chrome::NewTab(browser2.get());
browser2->window()->Show();
base::string16 title2 = ASCIIToUTF16("Test2");
NavigateAndCommitActiveTabWithTitle(browser2.get(), GURL("http://test2"),
title2);
// Check that the list contains now two entries - make furthermore sure that
// the active item is the first entry.
base::string16 two_menu_items[] = {title1, title2};
CheckAppMenu(launcher_controller_, item_browser, 2, two_menu_items);
// Apparently we have to close all tabs we have.
chrome::CloseTab(browser2.get());
}
// Check the multi profile case where only user related browsers should show up.
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeLauncherControllerImplTest,
BrowserMenuGenerationTwoUsers) {
// Create a browser item in the LauncherController.
InitLauncherController();
ash::ShelfItem item_browser;
item_browser.type = ash::TYPE_BROWSER_SHORTCUT;
item_browser.id =
launcher_controller_->GetShelfIDForAppID(extension_misc::kChromeAppId);
// Check that the menu is empty.
chrome::NewTab(browser());
CheckAppMenu(launcher_controller_, item_browser, 0, nullptr);
// Show the created |browser()| by showing its window.
browser()->window()->Show();
base::string16 title1 = ASCIIToUTF16("Test1");
NavigateAndCommitActiveTabWithTitle(browser(), GURL("http://test1"), title1);
base::string16 one_menu_item1[] = { title1 };
CheckAppMenu(launcher_controller_, item_browser, 1, one_menu_item1);
// Create a browser for another user and check that it is not included in the
// users running browser list.
std::string user2 = "user2";
TestingProfile* profile2 = CreateMultiUserProfile(user2);
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
std::unique_ptr<Browser> browser2(
CreateBrowserAndTabWithProfile(profile2, user2, "http://test2"));
base::string16 one_menu_item2[] = { ASCIIToUTF16(user2) };
CheckAppMenu(launcher_controller_, item_browser, 1, one_menu_item1);
// Switch to the other user and make sure that only that browser window gets
// shown.
SwitchActiveUser(account_id2);
CheckAppMenu(launcher_controller_, item_browser, 1, one_menu_item2);
// Transferred browsers of other users should not show up in the list.
chrome::MultiUserWindowManager::GetInstance()->ShowWindowForUser(
browser()->window()->GetNativeWindow(), account_id2);
CheckAppMenu(launcher_controller_, item_browser, 1, one_menu_item2);
chrome::CloseTab(browser2.get());
}
// Check that V1 apps are correctly reflected in the launcher menu using the
// refocus logic.
// Note that the extension matching logic is tested by the extension system
// and does not need a separate test here.
TEST_F(ChromeLauncherControllerImplTest, V1AppMenuGeneration) {
EXPECT_EQ(1U, chrome::GetTotalBrowserCount());
EXPECT_EQ(0, browser()->tab_strip_model()->count());
InitLauncherControllerWithBrowser();
// The model should only contain the browser shortcut and app list items.
EXPECT_EQ(2, model_->item_count());
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension3_->id()));
// Installing |extension3_| adds it to the launcher.
ash::ShelfID gmail_id = model_->next_id();
extension_service_->AddExtension(extension3_.get());
EXPECT_EQ(3, model_->item_count());
int gmail_index = model_->ItemIndexByID(gmail_id);
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[gmail_index].type);
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension3_->id()));
launcher_controller_->SetRefocusURLPatternForTest(gmail_id, GURL(gmail_url));
// Check the menu content.
ash::ShelfItem item_browser;
item_browser.type = ash::TYPE_BROWSER_SHORTCUT;
item_browser.id =
launcher_controller_->GetShelfIDForAppID(extension_misc::kChromeAppId);
ash::ShelfItem item_gmail;
item_gmail.type = ash::TYPE_PINNED_APP;
item_gmail.id = gmail_id;
CheckAppMenu(launcher_controller_, item_gmail, 0, nullptr);
// Set the gmail URL to a new tab.
base::string16 title1 = ASCIIToUTF16("Test1");
NavigateAndCommitActiveTabWithTitle(browser(), GURL(gmail_url), title1);
base::string16 one_menu_item[] = { title1 };
CheckAppMenu(launcher_controller_, item_gmail, 1, one_menu_item);
// Create one empty tab.
chrome::NewTab(browser());
base::string16 title2 = ASCIIToUTF16("Test2");
NavigateAndCommitActiveTabWithTitle(
browser(),
GURL("https://bla"),
title2);
// and another one with another gmail instance.
chrome::NewTab(browser());
base::string16 title3 = ASCIIToUTF16("Test3");
NavigateAndCommitActiveTabWithTitle(browser(), GURL(gmail_url), title3);
base::string16 two_menu_items[] = {title1, title3};
CheckAppMenu(launcher_controller_, item_gmail, 2, two_menu_items);
// Even though the item is in the V1 app list, it should also be in the
// browser list.
base::string16 browser_menu_item[] = {title3};
CheckAppMenu(launcher_controller_, item_browser, 1, browser_menu_item);
// Test that closing of (all) the item(s) does work (and all menus get
// updated properly).
launcher_controller_->Close(item_gmail.id);
CheckAppMenu(launcher_controller_, item_gmail, 0, nullptr);
base::string16 browser_menu_item2[] = { title2 };
CheckAppMenu(launcher_controller_, item_browser, 1, browser_menu_item2);
}
// Check the multi profile case where only user related apps should show up.
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeLauncherControllerImplTest,
V1AppMenuGenerationTwoUsers) {
// Create a browser item in the LauncherController.
InitLauncherController();
chrome::NewTab(browser());
// Installing |extension3_| adds it to the launcher.
ash::ShelfID gmail_id = model_->next_id();
extension_service_->AddExtension(extension3_.get());
EXPECT_EQ(3, model_->item_count());
int gmail_index = model_->ItemIndexByID(gmail_id);
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[gmail_index].type);
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension3_->id()));
launcher_controller_->SetRefocusURLPatternForTest(gmail_id, GURL(gmail_url));
// Check the menu content.
ash::ShelfItem item_browser;
item_browser.type = ash::TYPE_BROWSER_SHORTCUT;
item_browser.id =
launcher_controller_->GetShelfIDForAppID(extension_misc::kChromeAppId);
ash::ShelfItem item_gmail;
item_gmail.type = ash::TYPE_PINNED_APP;
item_gmail.id = gmail_id;
CheckAppMenu(launcher_controller_, item_gmail, 0, nullptr);
// Set the gmail URL to a new tab.
base::string16 title1 = ASCIIToUTF16("Test1");
NavigateAndCommitActiveTabWithTitle(browser(), GURL(gmail_url), title1);
base::string16 one_menu_item[] = { title1 };
CheckAppMenu(launcher_controller_, item_gmail, 1, one_menu_item);
// Create a second profile and switch to that user.
std::string user2 = "user2";
TestingProfile* profile2 = CreateMultiUserProfile(user2);
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
SwitchActiveUser(account_id2);
// No item should have content yet.
CheckAppMenu(launcher_controller_, item_browser, 0, nullptr);
CheckAppMenu(launcher_controller_, item_gmail, 0, nullptr);
// Transfer the browser of the first user - it should still not show up.
chrome::MultiUserWindowManager::GetInstance()->ShowWindowForUser(
browser()->window()->GetNativeWindow(), account_id2);
CheckAppMenu(launcher_controller_, item_browser, 0, nullptr);
CheckAppMenu(launcher_controller_, item_gmail, 0, nullptr);
}
// Check that V2 applications are creating items properly in the launcher when
// instantiated by the current user.
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeLauncherControllerImplTest,
V2AppHandlingTwoUsers) {
InitLauncherController();
// Create a profile for our second user (will be destroyed by the framework).
TestingProfile* profile2 = CreateMultiUserProfile("user2");
const AccountId account_id(
multi_user_util::GetAccountIdFromProfile(profile()));
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
// Check that there is a browser and a app launcher.
EXPECT_EQ(2, model_->item_count());
// Add a v2 app.
V2App v2_app(profile(), extension1_.get());
EXPECT_EQ(3, model_->item_count());
// After switching users the item should go away.
SwitchActiveUser(account_id2);
EXPECT_EQ(2, model_->item_count());
// And it should come back when switching back.
SwitchActiveUser(account_id);
EXPECT_EQ(3, model_->item_count());
}
// Check that V2 applications are creating items properly in edge cases:
// a background user creates a V2 app, gets active and inactive again and then
// deletes the app.
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeLauncherControllerImplTest,
V2AppHandlingTwoUsersEdgeCases) {
InitLauncherController();
// Create a profile for our second user (will be destroyed by the framework).
TestingProfile* profile2 = CreateMultiUserProfile("user2");
const AccountId account_id(
multi_user_util::GetAccountIdFromProfile(profile()));
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
// Check that there is a browser and a app launcher.
EXPECT_EQ(2, model_->item_count());
// Switch to an inactive user.
SwitchActiveUser(account_id2);
EXPECT_EQ(2, model_->item_count());
// Add the v2 app to the inactive user and check that no item was added to
// the launcher.
{
V2App v2_app(profile(), extension1_.get());
EXPECT_EQ(2, model_->item_count());
// Switch to the primary user and check that the item is shown.
SwitchActiveUser(account_id);
EXPECT_EQ(3, model_->item_count());
// Switch to the second user and check that the item goes away - even if the
// item gets closed.
SwitchActiveUser(account_id2);
EXPECT_EQ(2, model_->item_count());
}
// After the application was killed there should be still 2 items.
EXPECT_EQ(2, model_->item_count());
// Switching then back to the default user should not show the additional item
// anymore.
SwitchActiveUser(account_id);
EXPECT_EQ(2, model_->item_count());
}
// Check that V2 applications will be made visible on the target desktop if
// another window of the same type got previously teleported there.
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeLauncherControllerImplTest,
V2AppFollowsTeleportedWindow) {
InitLauncherController();
chrome::MultiUserWindowManager* manager =
chrome::MultiUserWindowManager::GetInstance();
// Create and add three users / profiles, and go to #1's desktop.
TestingProfile* profile1 = CreateMultiUserProfile("user-1");
TestingProfile* profile2 = CreateMultiUserProfile("user-2");
TestingProfile* profile3 = CreateMultiUserProfile("user-3");
const AccountId account_id1(
multi_user_util::GetAccountIdFromProfile(profile1));
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
const AccountId account_id3(
multi_user_util::GetAccountIdFromProfile(profile3));
SwitchActiveUser(account_id1);
// A v2 app for user #1 should be shown first and get hidden when switching to
// desktop #2.
V2App v2_app_1(profile1, extension1_.get());
EXPECT_TRUE(v2_app_1.window()->GetNativeWindow()->IsVisible());
SwitchActiveUser(account_id2);
EXPECT_FALSE(v2_app_1.window()->GetNativeWindow()->IsVisible());
// Add a v2 app for user #1 while on desktop #2 should not be shown.
V2App v2_app_2(profile1, extension1_.get());
EXPECT_FALSE(v2_app_1.window()->GetNativeWindow()->IsVisible());
EXPECT_FALSE(v2_app_2.window()->GetNativeWindow()->IsVisible());
// Teleport the app from user #1 to the desktop #2 should show it.
manager->ShowWindowForUser(v2_app_1.window()->GetNativeWindow(), account_id2);
EXPECT_TRUE(v2_app_1.window()->GetNativeWindow()->IsVisible());
EXPECT_FALSE(v2_app_2.window()->GetNativeWindow()->IsVisible());
// Creating a new application for user #1 on desktop #2 should teleport it
// there automatically.
V2App v2_app_3(profile1, extension1_.get());
EXPECT_TRUE(v2_app_1.window()->GetNativeWindow()->IsVisible());
EXPECT_FALSE(v2_app_2.window()->GetNativeWindow()->IsVisible());
EXPECT_TRUE(v2_app_3.window()->GetNativeWindow()->IsVisible());
// Switching back to desktop#1 and creating an app for user #1 should move
// the app on desktop #1.
SwitchActiveUser(account_id1);
V2App v2_app_4(profile1, extension1_.get());
EXPECT_FALSE(v2_app_1.window()->GetNativeWindow()->IsVisible());
EXPECT_TRUE(v2_app_2.window()->GetNativeWindow()->IsVisible());
EXPECT_FALSE(v2_app_3.window()->GetNativeWindow()->IsVisible());
EXPECT_TRUE(v2_app_4.window()->GetNativeWindow()->IsVisible());
// Switching to desktop #3 and creating an app for user #1 should place it on
// that user's desktop (#1).
SwitchActiveUser(account_id3);
V2App v2_app_5(profile1, extension1_.get());
EXPECT_FALSE(v2_app_5.window()->GetNativeWindow()->IsVisible());
SwitchActiveUser(account_id1);
EXPECT_TRUE(v2_app_5.window()->GetNativeWindow()->IsVisible());
// Switching to desktop #2, hiding the app window and creating an app should
// teleport there automatically.
SwitchActiveUser(account_id2);
v2_app_1.window()->Hide();
V2App v2_app_6(profile1, extension1_.get());
EXPECT_FALSE(v2_app_1.window()->GetNativeWindow()->IsVisible());
EXPECT_FALSE(v2_app_2.window()->GetNativeWindow()->IsVisible());
EXPECT_TRUE(v2_app_6.window()->GetNativeWindow()->IsVisible());
}
// Check that V2 applications hide correctly on the shelf when the app window
// is hidden.
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeLauncherControllerImplTest,
V2AppHiddenWindows) {
InitLauncherController();
TestingProfile* profile2 = CreateMultiUserProfile("user-2");
const AccountId account_id(
multi_user_util::GetAccountIdFromProfile(profile()));
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
SwitchActiveUser(account_id);
EXPECT_EQ(2, model_->item_count());
V2App v2_app_1(profile(), extension1_.get());
EXPECT_EQ(3, model_->item_count());
{
// Hide and show the app.
v2_app_1.window()->Hide();
EXPECT_EQ(2, model_->item_count());
v2_app_1.window()->Show(extensions::AppWindow::SHOW_ACTIVE);
EXPECT_EQ(3, model_->item_count());
}
{
// Switch user, hide and show the app and switch back.
SwitchActiveUser(account_id2);
EXPECT_EQ(2, model_->item_count());
v2_app_1.window()->Hide();
EXPECT_EQ(2, model_->item_count());
v2_app_1.window()->Show(extensions::AppWindow::SHOW_ACTIVE);
EXPECT_EQ(2, model_->item_count());
SwitchActiveUser(account_id);
EXPECT_EQ(3, model_->item_count());
}
{
// Switch user, hide the app, switch back and then show it again.
SwitchActiveUser(account_id2);
EXPECT_EQ(2, model_->item_count());
v2_app_1.window()->Hide();
EXPECT_EQ(2, model_->item_count());
SwitchActiveUser(account_id);
// The following expectation does not work in current impl. It was working
// before because MultiUserWindowManagerChromeOS is not attached to user
// associated with profile() hence not actually handling windows for the
// user. It is a real bug. See http://crbug.com/693634
// EXPECT_EQ(2, model_->item_count());
v2_app_1.window()->Show(extensions::AppWindow::SHOW_ACTIVE);
EXPECT_EQ(3, model_->item_count());
}
{
// Create a second app, hide and show it and then hide both apps.
V2App v2_app_2(profile(), extension1_.get());
EXPECT_EQ(3, model_->item_count());
v2_app_2.window()->Hide();
EXPECT_EQ(3, model_->item_count());
v2_app_2.window()->Show(extensions::AppWindow::SHOW_ACTIVE);
EXPECT_EQ(3, model_->item_count());
v2_app_1.window()->Hide();
v2_app_2.window()->Hide();
EXPECT_EQ(2, model_->item_count());
}
}
// Checks that the generated menu list properly activates items.
TEST_F(ChromeLauncherControllerImplTest, V1AppMenuExecution) {
InitLauncherControllerWithBrowser();
// Add |extension3_| to the launcher and add two items.
GURL gmail = GURL("https://mail.google.com/mail/u");
ash::ShelfID gmail_id = model_->next_id();
extension_service_->AddExtension(extension3_.get());
launcher_controller_->SetRefocusURLPatternForTest(gmail_id, GURL(gmail_url));
base::string16 title1 = ASCIIToUTF16("Test1");
NavigateAndCommitActiveTabWithTitle(browser(), GURL(gmail_url), title1);
chrome::NewTab(browser());
base::string16 title2 = ASCIIToUTF16("Test2");
NavigateAndCommitActiveTabWithTitle(browser(), GURL(gmail_url), title2);
// Check that the menu is properly set.
ash::ShelfItem item_gmail;
item_gmail.type = ash::TYPE_PINNED_APP;
item_gmail.id = gmail_id;
base::string16 two_menu_items[] = {title1, title2};
CheckAppMenu(launcher_controller_, item_gmail, 2, two_menu_items);
ash::ShelfItemDelegate* item_delegate =
model_->GetShelfItemDelegate(gmail_id);
ASSERT_TRUE(item_delegate);
EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
// Execute the second item in the menu, after the title and two separators,
// this shouldn't do anything since that item is already the active tab.
{
ash::ShelfApplicationMenuModel menu(
base::string16(),
launcher_controller_->GetAppMenuItemsForTesting(item_gmail),
item_delegate);
menu.ActivatedAt(4);
}
EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
// Execute the first item in the menu, after the title and two separators,
// this should activate the other tab.
{
ash::ShelfApplicationMenuModel menu(
base::string16(),
launcher_controller_->GetAppMenuItemsForTesting(item_gmail),
item_delegate);
menu.ActivatedAt(3);
}
EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
}
// Checks that the generated menu list properly deletes items.
TEST_F(ChromeLauncherControllerImplTest, V1AppMenuDeletionExecution) {
InitLauncherControllerWithBrowser();
// Add |extension3_| to the launcher and add two items.
GURL gmail = GURL("https://mail.google.com/mail/u");
ash::ShelfID gmail_id = model_->next_id();
extension_service_->AddExtension(extension3_.get());
launcher_controller_->SetRefocusURLPatternForTest(gmail_id, GURL(gmail_url));
base::string16 title1 = ASCIIToUTF16("Test1");
NavigateAndCommitActiveTabWithTitle(browser(), GURL(gmail_url), title1);
chrome::NewTab(browser());
base::string16 title2 = ASCIIToUTF16("Test2");
NavigateAndCommitActiveTabWithTitle(browser(), GURL(gmail_url), title2);
// Check that the menu is properly set.
ash::ShelfItem item_gmail;
item_gmail.type = ash::TYPE_PINNED_APP;
item_gmail.id = gmail_id;
base::string16 two_menu_items[] = {title1, title2};
CheckAppMenu(launcher_controller_, item_gmail, 2, two_menu_items);
ash::ShelfItemDelegate* item_delegate =
model_->GetShelfItemDelegate(gmail_id);
ASSERT_TRUE(item_delegate);
int tabs = browser()->tab_strip_model()->count();
// Activate the proper tab through the menu item.
{
ash::MenuItemList items =
launcher_controller_->GetAppMenuItemsForTesting(item_gmail);
item_delegate->ExecuteCommand(items[1]->command_id, ui::EF_NONE);
EXPECT_EQ(tabs, browser()->tab_strip_model()->count());
}
// Delete one tab through the menu item.
{
ash::MenuItemList items =
launcher_controller_->GetAppMenuItemsForTesting(item_gmail);
item_delegate->ExecuteCommand(items[1]->command_id, ui::EF_SHIFT_DOWN);
EXPECT_EQ(--tabs, browser()->tab_strip_model()->count());
}
}
// Tests that panels create launcher items correctly
TEST_F(ChromeLauncherControllerImplTest, AppPanels) {
InitLauncherController();
model_observer_->clear_counts();
const std::string app_id = extension1_->id();
// app_icon_loader is owned by ChromeLauncherControllerImpl.
TestAppIconLoaderImpl* app_icon_loader = new TestAppIconLoaderImpl();
app_icon_loader->AddSupportedApp(app_id);
SetAppIconLoader(std::unique_ptr<AppIconLoader>(app_icon_loader));
// Make an app panel; the ShelfItem is added by ash::ShelfWindowWatcher.
std::unique_ptr<V2App> app_panel1 = base::MakeUnique<V2App>(
profile(), extension1_.get(), extensions::AppWindow::WINDOW_TYPE_PANEL);
EXPECT_TRUE(app_panel1->window()->GetNativeWindow()->IsVisible());
int panel_index = model_observer_->last_index();
EXPECT_EQ(1, model_observer_->added());
EXPECT_EQ(1, app_icon_loader->fetch_count());
model_observer_->clear_counts();
// App panels should have a separate identifier than the app id
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(app_id));
// Setting the app image should not change the panel, which has a window icon.
gfx::ImageSkia image;
launcher_controller_->OnAppImageUpdated(app_id, image);
EXPECT_EQ(0, model_observer_->changed());
model_observer_->clear_counts();
// Make a second app panel and verify that it gets the same index as the first
// panel, being added to the left of the existing panel.
std::unique_ptr<V2App> app_panel2 = base::MakeUnique<V2App>(
profile(), extension2_.get(), extensions::AppWindow::WINDOW_TYPE_PANEL);
EXPECT_EQ(panel_index, model_observer_->last_index());
EXPECT_EQ(1, model_observer_->added());
model_observer_->clear_counts();
app_panel1.reset();
app_panel2.reset();
EXPECT_EQ(2, model_observer_->removed());
}
// Tests that the Gmail extension matches more than the app itself claims with
// the manifest file.
TEST_F(ChromeLauncherControllerImplTest, GmailMatching) {
InitLauncherControllerWithBrowser();
// Create a Gmail browser tab.
chrome::NewTab(browser());
base::string16 title = ASCIIToUTF16("Test");
NavigateAndCommitActiveTabWithTitle(browser(), GURL(gmail_url), title);
content::WebContents* content =
browser()->tab_strip_model()->GetActiveWebContents();
// Check that the launcher controller does not recognize the running app.
EXPECT_FALSE(launcher_controller_->ContentCanBeHandledByGmailApp(content));
// Installing |extension3_| adds it to the launcher.
ash::ShelfID gmail_id = model_->next_id();
extension_service_->AddExtension(extension3_.get());
EXPECT_EQ(3, model_->item_count());
int gmail_index = model_->ItemIndexByID(gmail_id);
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[gmail_index].type);
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension3_->id()));
// Check that it is now handled.
EXPECT_TRUE(launcher_controller_->ContentCanBeHandledByGmailApp(content));
// Check also that the app has detected that properly.
ash::ShelfItem item_gmail;
item_gmail.type = ash::TYPE_PINNED_APP;
item_gmail.id = gmail_id;
EXPECT_EQ(1U,
launcher_controller_->GetAppMenuItemsForTesting(item_gmail).size());
}
// Tests that the Gmail extension does not match the offline verison.
TEST_F(ChromeLauncherControllerImplTest, GmailOfflineMatching) {
InitLauncherControllerWithBrowser();
// Create a Gmail browser tab.
chrome::NewTab(browser());
base::string16 title = ASCIIToUTF16("Test");
NavigateAndCommitActiveTabWithTitle(browser(),
GURL(offline_gmail_url),
title);
content::WebContents* content =
browser()->tab_strip_model()->GetActiveWebContents();
// Installing |extension3_| adds it to the launcher.
ash::ShelfID gmail_id = model_->next_id();
extension_service_->AddExtension(extension3_.get());
EXPECT_EQ(3, model_->item_count());
int gmail_index = model_->ItemIndexByID(gmail_id);
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[gmail_index].type);
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension3_->id()));
// The content should not be able to be handled by the app.
EXPECT_FALSE(launcher_controller_->ContentCanBeHandledByGmailApp(content));
}
// Verify that the launcher item positions are persisted and restored.
TEST_F(ChromeLauncherControllerImplTest, PersistLauncherItemPositions) {
InitLauncherController();
TestLauncherControllerHelper* helper = new TestLauncherControllerHelper;
SetLauncherControllerHelper(helper);
EXPECT_EQ(ash::TYPE_APP_LIST, model_->items()[0].type);
EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, model_->items()[1].type);
TabStripModel* tab_strip_model = browser()->tab_strip_model();
EXPECT_EQ(0, tab_strip_model->count());
chrome::NewTab(browser());
chrome::NewTab(browser());
EXPECT_EQ(2, tab_strip_model->count());
helper->SetAppID(tab_strip_model->GetWebContentsAt(0), "1");
helper->SetAppID(tab_strip_model->GetWebContentsAt(1), "2");
EXPECT_FALSE(launcher_controller_->IsAppPinned("1"));
launcher_controller_->PinAppWithID("1");
EXPECT_TRUE(launcher_controller_->IsAppPinned("1"));
launcher_controller_->PinAppWithID("2");
EXPECT_EQ(ash::TYPE_APP_LIST, model_->items()[0].type);
EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, model_->items()[1].type);
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[2].type);
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[3].type);
// Move browser shortcut item from index 1 to index 3.
model_->Move(1, 3);
EXPECT_EQ(ash::TYPE_APP_LIST, model_->items()[0].type);
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[1].type);
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[2].type);
EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, model_->items()[3].type);
RecreateLauncherController();
helper = new TestLauncherControllerHelper(profile());
helper->SetAppID(tab_strip_model->GetWebContentsAt(0), "1");
helper->SetAppID(tab_strip_model->GetWebContentsAt(1), "2");
SetLauncherControllerHelper(helper);
launcher_controller_->Init();
// Check ShelfItems are restored after resetting ChromeLauncherControllerImpl.
EXPECT_EQ(ash::TYPE_APP_LIST, model_->items()[0].type);
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[1].type);
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[2].type);
EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, model_->items()[3].type);
}
// Verifies pinned apps are persisted and restored.
TEST_F(ChromeLauncherControllerImplTest, PersistPinned) {
InitLauncherControllerWithBrowser();
size_t initial_size = model_->items().size();
TabStripModel* tab_strip_model = browser()->tab_strip_model();
EXPECT_EQ(1, tab_strip_model->count());
TestLauncherControllerHelper* helper = new TestLauncherControllerHelper;
helper->SetAppID(tab_strip_model->GetWebContentsAt(0), "1");
SetLauncherControllerHelper(helper);
// app_icon_loader is owned by ChromeLauncherControllerImpl.
TestAppIconLoaderImpl* app_icon_loader = new TestAppIconLoaderImpl;
app_icon_loader->AddSupportedApp("1");
SetAppIconLoader(std::unique_ptr<AppIconLoader>(app_icon_loader));
EXPECT_EQ(0, app_icon_loader->fetch_count());
launcher_controller_->PinAppWithID("1");
ash::ShelfID id = launcher_controller_->GetShelfIDForAppID("1");
int app_index = model_->ItemIndexByID(id);
EXPECT_EQ(1, app_icon_loader->fetch_count());
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[app_index].type);
EXPECT_TRUE(launcher_controller_->IsAppPinned("1"));
EXPECT_FALSE(launcher_controller_->IsAppPinned("0"));
EXPECT_EQ(initial_size + 1, model_->items().size());
RecreateLauncherController();
helper = new TestLauncherControllerHelper(profile());
helper->SetAppID(tab_strip_model->GetWebContentsAt(0), "1");
SetLauncherControllerHelper(helper);
// app_icon_loader is owned by ChromeLauncherControllerImpl.
app_icon_loader = new TestAppIconLoaderImpl;
app_icon_loader->AddSupportedApp("1");
SetAppIconLoader(std::unique_ptr<AppIconLoader>(app_icon_loader));
launcher_controller_->Init();
EXPECT_EQ(1, app_icon_loader->fetch_count());
ASSERT_EQ(initial_size + 1, model_->items().size());
EXPECT_TRUE(launcher_controller_->IsAppPinned("1"));
EXPECT_FALSE(launcher_controller_->IsAppPinned("0"));
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[app_index].type);
launcher_controller_->UnpinAppWithID("1");
ASSERT_EQ(initial_size, model_->items().size());
}
TEST_F(ChromeLauncherControllerImplTest, MultipleAppIconLoaders) {
InitLauncherControllerWithBrowser();
const std::string app_id1 = extension1_->id();
const std::string app_id2 = extension2_->id();
const std::string app_id3 = extension3_->id();
// app_icon_loader1 and app_icon_loader2 are owned by
// ChromeLauncherControllerImpl.
TestAppIconLoaderImpl* app_icon_loader1 = new TestAppIconLoaderImpl();
TestAppIconLoaderImpl* app_icon_loader2 = new TestAppIconLoaderImpl();
app_icon_loader1->AddSupportedApp(app_id1);
app_icon_loader2->AddSupportedApp(app_id2);
SetAppIconLoaders(std::unique_ptr<AppIconLoader>(app_icon_loader1),
std::unique_ptr<AppIconLoader>(app_icon_loader2));
const ash::ShelfID shelfId3 = launcher_controller_->CreateAppLauncherItem(
base::MakeUnique<ExtensionAppWindowLauncherItemController>(
ash::AppLaunchId(app_id3)),
ash::STATUS_RUNNING);
EXPECT_EQ(0, app_icon_loader1->fetch_count());
EXPECT_EQ(0, app_icon_loader1->clear_count());
EXPECT_EQ(0, app_icon_loader2->fetch_count());
EXPECT_EQ(0, app_icon_loader2->clear_count());
const ash::ShelfID shelfId2 = launcher_controller_->CreateAppLauncherItem(
base::MakeUnique<ExtensionAppWindowLauncherItemController>(
ash::AppLaunchId(app_id2)),
ash::STATUS_RUNNING);
EXPECT_EQ(0, app_icon_loader1->fetch_count());
EXPECT_EQ(0, app_icon_loader1->clear_count());
EXPECT_EQ(1, app_icon_loader2->fetch_count());
EXPECT_EQ(0, app_icon_loader2->clear_count());
const ash::ShelfID shelfId1 = launcher_controller_->CreateAppLauncherItem(
base::MakeUnique<ExtensionAppWindowLauncherItemController>(
ash::AppLaunchId(app_id1)),
ash::STATUS_RUNNING);
EXPECT_EQ(1, app_icon_loader1->fetch_count());
EXPECT_EQ(0, app_icon_loader1->clear_count());
EXPECT_EQ(1, app_icon_loader2->fetch_count());
EXPECT_EQ(0, app_icon_loader2->clear_count());
launcher_controller_->CloseLauncherItem(shelfId1);
EXPECT_EQ(1, app_icon_loader1->fetch_count());
EXPECT_EQ(1, app_icon_loader1->clear_count());
EXPECT_EQ(1, app_icon_loader2->fetch_count());
EXPECT_EQ(0, app_icon_loader2->clear_count());
launcher_controller_->CloseLauncherItem(shelfId2);
EXPECT_EQ(1, app_icon_loader1->fetch_count());
EXPECT_EQ(1, app_icon_loader1->clear_count());
EXPECT_EQ(1, app_icon_loader2->fetch_count());
EXPECT_EQ(1, app_icon_loader2->clear_count());
launcher_controller_->CloseLauncherItem(shelfId3);
EXPECT_EQ(1, app_icon_loader1->fetch_count());
EXPECT_EQ(1, app_icon_loader1->clear_count());
EXPECT_EQ(1, app_icon_loader2->fetch_count());
EXPECT_EQ(1, app_icon_loader2->clear_count());
}
TEST_P(ChromeLauncherControllerImplWithArcTest, ArcAppPinPolicy) {
InitLauncherControllerWithBrowser();
arc::mojom::AppInfo appinfo = CreateAppInfo(
"Some App", "SomeActivity", "com.example.app", OrientationLock::NONE);
const std::string app_id = AddArcAppAndShortcut(appinfo);
// Set policy, that makes pins ARC app. Unlike native extension, for ARC app
// package_name (not hash) specified as id. In this test we check that
// by hash we can determine that appropriate package was set by policy.
base::ListValue policy_value;
InsertPrefValue(&policy_value, 0, appinfo.package_name);
profile()->GetTestingPrefService()->SetManagedPref(
prefs::kPolicyPinnedLauncherApps, policy_value.CreateDeepCopy());
EXPECT_TRUE(launcher_controller_->IsAppPinned(app_id));
EXPECT_EQ(AppListControllerDelegate::PIN_FIXED,
GetPinnableForAppID(app_id, profile()));
}
TEST_P(ChromeLauncherControllerImplWithArcTest, ArcManaged) {
// TODO(victorhsieh): Implement opt-in and opt-out.
if (arc::ShouldArcAlwaysStart())
return;
extension_service_->AddExtension(arc_support_host_.get());
// Test enables ARC, so turn it off for initial values.
EnablePlayStore(false);
InitLauncherController();
// To prevent import legacy pins each time.
// Initially pins are imported from legacy pref based model.
StartPrefSyncService(syncer::SyncDataList());
// Initial run, ARC is not managed and disabled, Play Store pin should be
// available.
ValidateArcState(false, false, arc::ArcSessionManager::State::STOPPED,
"AppList, Chrome, Play Store");
// ARC is managed and enabled, Play Store pin should be available.
// Note: NEGOTIATING_TERMS_OF_SERVICE here means that opt-in flow starts.
profile()->GetTestingPrefService()->SetManagedPref(
prefs::kArcEnabled, base::MakeUnique<base::Value>(true));
base::RunLoop().RunUntilIdle();
ValidateArcState(true, true,
arc::ArcSessionManager::State::NEGOTIATING_TERMS_OF_SERVICE,
"AppList, Chrome, Play Store");
// ARC is managed and disabled, Play Store pin should not be available.
profile()->GetTestingPrefService()->SetManagedPref(
prefs::kArcEnabled, base::MakeUnique<base::Value>(false));
base::RunLoop().RunUntilIdle();
ValidateArcState(false, true, arc::ArcSessionManager::State::STOPPED,
"AppList, Chrome");
// ARC is not managed and disabled, Play Store pin should be available.
profile()->GetTestingPrefService()->RemoveManagedPref(prefs::kArcEnabled);
base::RunLoop().RunUntilIdle();
ValidateArcState(false, false, arc::ArcSessionManager::State::STOPPED,
"AppList, Chrome, Play Store");
// ARC is not managed and enabled, Play Store pin should be available.
EnablePlayStore(true);
ValidateArcState(true, false,
arc::ArcSessionManager::State::NEGOTIATING_TERMS_OF_SERVICE,
"AppList, Chrome, Play Store");
// User disables ARC. ARC is not managed and disabled, Play Store pin should
// be automatically removed.
EnablePlayStore(false);
ValidateArcState(false, false, arc::ArcSessionManager::State::STOPPED,
"AppList, Chrome");
// Even if re-enable it again, Play Store pin does not appear automatically.
EnablePlayStore(true);
ValidateArcState(true, false,
arc::ArcSessionManager::State::NEGOTIATING_TERMS_OF_SERVICE,
"AppList, Chrome");
}
// Test the application menu of a shelf item with multiple ARC windows.
TEST_P(ChromeLauncherControllerImplWithArcTest, ShelfItemWithMultipleWindows) {
InitLauncherControllerWithBrowser();
arc::mojom::AppInfo appinfo =
CreateAppInfo("Test1", "test", "com.example.app", OrientationLock::NONE);
AddArcAppAndShortcut(appinfo);
// Widgets will be deleted by the system.
NotifyOnTaskCreated(appinfo, 1 /* task_id */);
views::Widget* window1 = CreateArcWindow("org.chromium.arc.1");
ASSERT_TRUE(window1);
EXPECT_TRUE(window1->IsActive());
NotifyOnTaskCreated(appinfo, 2 /* task_id */);
views::Widget* window2 = CreateArcWindow("org.chromium.arc.2");
ASSERT_TRUE(window2);
EXPECT_FALSE(window1->IsActive());
EXPECT_TRUE(window2->IsActive());
const std::string app_id = ArcAppTest::GetAppId(appinfo);
const ash::ShelfID shelf_id =
launcher_controller_->GetShelfIDForAppID(app_id);
ash::ShelfItemDelegate* item_delegate =
model_->GetShelfItemDelegate(shelf_id);
ASSERT_TRUE(item_delegate);
// Selecting the item will show its application menu. It does not change the
// active window.
SelectItem(item_delegate);
EXPECT_FALSE(window1->IsActive());
EXPECT_TRUE(window2->IsActive());
// Command ids are just app window indices. Note, apps are registered in
// opposite order. Last created goes in front.
ash::MenuItemList items = item_delegate->GetAppMenuItems(0);
ASSERT_EQ(items.size(), 2U);
EXPECT_EQ(items[0]->command_id, 0U);
EXPECT_EQ(items[1]->command_id, 1U);
// Execute command to activate first window.
item_delegate->ExecuteCommand(items[1]->command_id, 0);
EXPECT_TRUE(window1->IsActive());
EXPECT_FALSE(window2->IsActive());
// Selecting the item will show its application menu. It does not change the
// active window.
SelectItem(item_delegate);
EXPECT_TRUE(window1->IsActive());
EXPECT_FALSE(window2->IsActive());
// Execute command to activate second window.
item_delegate->ExecuteCommand(items[0]->command_id, 0);
EXPECT_FALSE(window1->IsActive());
EXPECT_TRUE(window2->IsActive());
}
namespace {
class ChromeLauncherControllerOrientationTest
: public ChromeLauncherControllerImplWithArcTest {
public:
ChromeLauncherControllerOrientationTest() {}
~ChromeLauncherControllerOrientationTest() override {}
protected:
void InitApps() {
appinfo_none_ =
CreateAppInfo("None", "None", "com.example.app", OrientationLock::NONE);
appinfo_landscape_ =
CreateAppInfo("Landscape", "Landscape", "com.example.app",
OrientationLock::LANDSCAPE);
appinfo_portrait_ = CreateAppInfo("Portrait", "Portrait", "com.example.app",
OrientationLock::PORTRAIT);
appinfo_current_ = CreateAppInfo(
"LockCurrent", "current", "com.example.app", OrientationLock::CURRENT);
AddArcAppAndShortcut(appinfo_none_);
AddArcAppAndShortcut(appinfo_landscape_);
AddArcAppAndShortcut(appinfo_portrait_);
AddArcAppAndShortcut(appinfo_current_);
ash::ScreenOrientationController* controller =
ash::Shell::Get()->screen_orientation_controller();
// Creating a window with NONE orientation will not lock the screen.
window_none_ = CreateArcWindow(window_app_id_none_);
NotifyOnTaskCreated(appinfo_none_, task_id_none_);
EXPECT_FALSE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_0,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
// Create a arc window with PORTRAIT orientation locks the screen to 90.
window_portrait_ = CreateArcWindow(window_app_id_portrait_);
NotifyOnTaskCreated(appinfo_portrait_, task_id_portrait_);
EXPECT_TRUE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_90,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
// Create a arc window with LANDSCAPE orientation locks the screen to 0.
window_landscape_ = CreateArcWindow(window_app_id_landscape_);
NotifyOnTaskCreated(appinfo_landscape_, task_id_landscape_);
EXPECT_TRUE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_0,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
}
int32_t task_id_none_ = 1;
int32_t task_id_landscape_ = 2;
int32_t task_id_portrait_ = 3;
int32_t task_id_current_ = 4;
// This needs to be kept on the instance because window's property has
// refeference to this.
std::string window_app_id_none_ = {"org.chromium.arc.1"};
std::string window_app_id_landscape_ = {"org.chromium.arc.2"};
std::string window_app_id_portrait_ = {"org.chromium.arc.3"};
std::string window_app_id_current_ = {"org.chromium.arc.4"};
arc::mojom::AppInfo appinfo_none_;
arc::mojom::AppInfo appinfo_landscape_;
arc::mojom::AppInfo appinfo_portrait_;
arc::mojom::AppInfo appinfo_current_;
views::Widget* window_none_ = nullptr;
views::Widget* window_landscape_ = nullptr;
views::Widget* window_portrait_ = nullptr;
views::Widget* window_current_ = nullptr;
private:
DISALLOW_COPY_AND_ASSIGN(ChromeLauncherControllerOrientationTest);
};
INSTANTIATE_TEST_CASE_P(,
ChromeLauncherControllerOrientationTest,
::testing::Bool());
class ChromeLauncherControllerArcDefaultAppsTest
: public ChromeLauncherControllerImplTest,
public ::testing::WithParamInterface<bool> {
public:
ChromeLauncherControllerArcDefaultAppsTest() {}
~ChromeLauncherControllerArcDefaultAppsTest() override {}
protected:
void SetUp() override {
if (GetParam())
arc::SetArcAlwaysStartForTesting();
ArcDefaultAppList::UseTestAppsDirectory();
ChromeLauncherControllerImplTest::SetUp();
}
private:
DISALLOW_COPY_AND_ASSIGN(ChromeLauncherControllerArcDefaultAppsTest);
};
INSTANTIATE_TEST_CASE_P(,
ChromeLauncherControllerArcDefaultAppsTest,
::testing::Bool());
} // namespace
TEST_P(ChromeLauncherControllerOrientationTest,
ArcOrientationLockBeforeWindowReady) {
ASSERT_TRUE(display::Display::HasInternalDisplay());
extension_service_->AddExtension(arc_support_host_.get());
EnablePlayStore(true);
InitLauncherController();
ash::ScreenOrientationController* controller =
ash::Shell::Get()->screen_orientation_controller();
std::string app_id1("org.chromium.arc.1");
int task_id1 = 1;
arc::mojom::AppInfo appinfo1 =
CreateAppInfo("Test1", "test", "com.example.app", OrientationLock::NONE);
AddArcAppAndShortcut(appinfo1);
NotifyOnTaskCreated(appinfo1, task_id1);
NotifyOnTaskOrientationLockRequested(task_id1, OrientationLock::PORTRAIT);
// Widgets will be deleted by the system.
CreateArcWindow(app_id1);
EXPECT_FALSE(controller->rotation_locked());
EnableTabletMode(true);
EXPECT_TRUE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_90,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
std::string app_id2("org.chromium.arc.2");
int task_id2 = 2;
arc::mojom::AppInfo appinfo2 =
CreateAppInfo("Test2", "test", "com.example.app", OrientationLock::NONE);
// Create in tablet mode.
AddArcAppAndShortcut(appinfo2);
NotifyOnTaskCreated(appinfo2, task_id2);
NotifyOnTaskOrientationLockRequested(task_id2, OrientationLock::LANDSCAPE);
EXPECT_TRUE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_90,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
// The screen will be locked when the window is created.
CreateArcWindow(app_id2);
EXPECT_TRUE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_0,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
}
TEST_P(ChromeLauncherControllerOrientationTest, ArcOrientationLock) {
ASSERT_TRUE(display::Display::HasInternalDisplay());
extension_service_->AddExtension(arc_support_host_.get());
EnablePlayStore(true);
EnableTabletMode(true);
InitLauncherController();
InitApps();
ash::ScreenOrientationController* controller =
ash::Shell::Get()->screen_orientation_controller();
// Activating a window with NON orientation unlocks the screen.
window_none_->Activate();
EXPECT_FALSE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_0,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
// Activating a window with PORTRAIT orientation locks the screen to 90.
window_portrait_->Activate();
EXPECT_TRUE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_90,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
// Disable Tablet mode, and make sure the screen is unlocked.
EnableTabletMode(false);
EXPECT_FALSE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_0,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
// Re-enable Tablet mode, and make sure the screen is locked to 90.
EnableTabletMode(true);
EXPECT_TRUE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_90,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
window_portrait_->Activate();
EXPECT_TRUE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_90,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
window_landscape_->Activate();
EXPECT_TRUE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_0,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
// OnTaskOrientationLockRequested can overwrite the current lock.
NotifyOnTaskOrientationLockRequested(task_id_landscape_,
OrientationLock::NONE);
EXPECT_FALSE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_0,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
NotifyOnTaskOrientationLockRequested(task_id_landscape_,
OrientationLock::PORTRAIT);
EXPECT_TRUE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_90,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
// Non active window won't change the lock.
NotifyOnTaskOrientationLockRequested(task_id_none_,
OrientationLock::LANDSCAPE);
EXPECT_TRUE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_90,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
// But activating it will change the locked orinetation.
window_none_->Activate();
EXPECT_TRUE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_0,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
// OnTaskOrientationLockRequested will not lock the screen in non Tablet mode.
EnableTabletMode(false);
EXPECT_FALSE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_0,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
NotifyOnTaskOrientationLockRequested(task_id_none_,
OrientationLock::PORTRAIT);
EXPECT_FALSE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_0,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
// But it remembers the orientation lock and use it when Tablet mode is
// enabled.
EnableTabletMode(true);
EXPECT_TRUE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_90,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
// Manually unlock first.
NotifyOnTaskOrientationLockRequested(task_id_none_, OrientationLock::NONE);
EXPECT_FALSE(controller->rotation_locked());
}
TEST_P(ChromeLauncherControllerArcDefaultAppsTest, DefaultApps) {
arc_test_.SetUp(profile());
InitLauncherController();
// TODO(crbug.com/709297): Fix this workaround to prevent a TearDown crash.
std::vector<std::unique_ptr<AppIconLoader>> no_loaders;
launcher_controller_->SetAppIconLoadersForTest(no_loaders);
ArcAppListPrefs* const prefs = arc_test_.arc_app_list_prefs();
EnablePlayStore(false);
EXPECT_FALSE(arc::IsArcPlayStoreEnabledForProfile(profile()));
ASSERT_TRUE(prefs->GetAppIds().size());
const std::string app_id =
ArcAppTest::GetAppId(arc_test_.fake_default_apps()[0]);
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(app_id));
EXPECT_TRUE(arc::LaunchApp(profile(), app_id, ui::EF_LEFT_MOUSE_BUTTON));
EXPECT_TRUE(arc::IsArcPlayStoreEnabledForProfile(profile()));
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(app_id));
// Stop ARC again. Shelf item should go away.
EnablePlayStore(false);
EXPECT_EQ(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(app_id));
EXPECT_TRUE(arc::LaunchApp(profile(), app_id, ui::EF_LEFT_MOUSE_BUTTON));
EXPECT_TRUE(arc::IsArcPlayStoreEnabledForProfile(profile()));
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(app_id));
EXPECT_TRUE(launcher_controller_->GetArcDeferredLauncher()->HasApp(app_id));
std::string window_app_id("org.chromium.arc.1");
CreateArcWindow(window_app_id);
arc_test_.app_instance()->SendTaskCreated(1, arc_test_.fake_default_apps()[0],
std::string());
EXPECT_NE(ash::kInvalidShelfID,
launcher_controller_->GetShelfIDForAppID(app_id));
EXPECT_FALSE(launcher_controller_->GetArcDeferredLauncher()->HasApp(app_id));
}
TEST_P(ChromeLauncherControllerArcDefaultAppsTest, PlayStoreDeferredLaunch) {
// Add ARC host app to enable Play Store default app.
extension_service_->AddExtension(arc_support_host_.get());
arc_test_.SetUp(profile());
ArcAppListPrefs* const prefs = arc_test_.arc_app_list_prefs();
EXPECT_TRUE(prefs->IsRegistered(arc::kPlayStoreAppId));
InitLauncherController();
EnablePlayStore(true);
// Pin Play Store. It should be pinned but not scheduled for deferred launch.
launcher_controller_->PinAppWithID(arc::kPlayStoreAppId);
EXPECT_TRUE(launcher_controller_->IsAppPinned(arc::kPlayStoreAppId));
EXPECT_FALSE(launcher_controller_->GetArcDeferredLauncher()->HasApp(
arc::kPlayStoreAppId));
// Simulate click. This should schedule Play Store for deferred launch.
ash::ShelfItemDelegate* item_delegate = model_->GetShelfItemDelegate(
launcher_controller_->GetShelfIDForAppID(arc::kPlayStoreAppId));
EXPECT_TRUE(item_delegate);
SelectItem(item_delegate);
EXPECT_TRUE(launcher_controller_->IsAppPinned(arc::kPlayStoreAppId));
EXPECT_TRUE(launcher_controller_->GetArcDeferredLauncher()->HasApp(
arc::kPlayStoreAppId));
}
// Checks the case when several app items have the same ordinal position (which
// is valid case).
TEST_F(ChromeLauncherControllerImplTest, CheckPositionConflict) {
InitLauncherController();
extension_service_->AddExtension(extension1_.get());
extension_service_->AddExtension(extension2_.get());
extension_service_->AddExtension(extension3_.get());
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 0, extension_misc::kChromeAppId);
InsertAddPinChange(&sync_list, 1, extension1_->id());
InsertAddPinChange(&sync_list, 1, extension2_->id());
InsertAddPinChange(&sync_list, 1, extension3_->id());
SendPinChanges(sync_list, true);
EXPECT_EQ("AppList, Chrome, App1, App2, App3", GetPinnedAppStatus());
const syncer::StringOrdinal position_chrome =
app_service_->GetPinPosition(extension_misc::kChromeAppId);
const syncer::StringOrdinal position_1 =
app_service_->GetPinPosition(extension1_->id());
const syncer::StringOrdinal position_2 =
app_service_->GetPinPosition(extension2_->id());
const syncer::StringOrdinal position_3 =
app_service_->GetPinPosition(extension3_->id());
EXPECT_TRUE(position_chrome.LessThan(position_1));
EXPECT_TRUE(position_1.Equals(position_2));
EXPECT_TRUE(position_2.Equals(position_3));
// Move Chrome between App1 and App2.
// Note, move target_index is in context when moved element is removed from
// array first.
model_->Move(1, 2);
EXPECT_EQ("AppList, App1, Chrome, App2, App3", GetPinnedAppStatus());
// Expect sync positions for only Chrome is updated and its resolution is
// after all duplicated ordinals.
EXPECT_TRUE(position_3.LessThan(
app_service_->GetPinPosition(extension_misc::kChromeAppId)));
EXPECT_TRUE(
position_1.Equals(app_service_->GetPinPosition(extension1_->id())));
EXPECT_TRUE(
position_1.Equals(app_service_->GetPinPosition(extension1_->id())));
EXPECT_TRUE(
position_2.Equals(app_service_->GetPinPosition(extension2_->id())));
EXPECT_TRUE(
position_3.Equals(app_service_->GetPinPosition(extension3_->id())));
}
// Test the case when sync app is turned off and we need to use local copy to
// support user's pins.
TEST_F(ChromeLauncherControllerImplTest, SyncOffLocalUpdate) {
InitLauncherController();
extension_service_->AddExtension(extension1_.get());
extension_service_->AddExtension(extension2_.get());
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 0, extension_misc::kChromeAppId);
InsertAddPinChange(&sync_list, 1, extension1_->id());
InsertAddPinChange(&sync_list, 1, extension2_->id());
SendPinChanges(sync_list, true);
EXPECT_EQ("AppList, Chrome, App1, App2", GetPinnedAppStatus());
syncer::SyncDataList copy_sync_list =
app_service_->GetAllSyncData(syncer::APP_LIST);
app_service_->StopSyncing(syncer::APP_LIST);
RecreateLauncherController()->Init();
// Pinned state should not change.
EXPECT_EQ("AppList, Chrome, App1, App2", GetPinnedAppStatus());
launcher_controller_->UnpinAppWithID(extension2_->id());
EXPECT_EQ("AppList, Chrome, App1", GetPinnedAppStatus());
// Resume syncing and sync information overrides local copy.
StartAppSyncService(copy_sync_list);
EXPECT_EQ("AppList, Chrome, App1, App2", GetPinnedAppStatus());
}
// A test ShelfController implementation that tracks alignment and auto-hide.
class TestShelfController : public ash::mojom::ShelfController {
public:
TestShelfController() : binding_(this) {}
~TestShelfController() override {}
ash::ShelfAlignment alignment() const { return alignment_; }
ash::ShelfAutoHideBehavior auto_hide() const { return auto_hide_; }
size_t alignment_change_count() const { return alignment_change_count_; }
size_t auto_hide_change_count() const { return auto_hide_change_count_; }
ash::mojom::ShelfControllerPtr CreateInterfacePtrAndBind() {
return binding_.CreateInterfacePtrAndBind();
}
// ash::mojom::ShelfController:
void AddObserver(
ash::mojom::ShelfObserverAssociatedPtrInfo observer) override {
observer_.Bind(std::move(observer));
}
void SetAlignment(ash::ShelfAlignment alignment,
int64_t display_id) override {
alignment_change_count_++;
alignment_ = alignment;
observer_->OnAlignmentChanged(alignment_, display_id);
}
void SetAutoHideBehavior(ash::ShelfAutoHideBehavior auto_hide,
int64_t display_id) override {
auto_hide_change_count_++;
auto_hide_ = auto_hide;
observer_->OnAutoHideBehaviorChanged(auto_hide_, display_id);
}
void PinItem(
const ash::ShelfItem& item,
ash::mojom::ShelfItemDelegateAssociatedPtrInfo delegate) override {}
void UnpinItem(const std::string& app_id) override {}
void SetItemImage(const std::string& app_id, const SkBitmap& image) override {
}
private:
ash::ShelfAlignment alignment_ = ash::SHELF_ALIGNMENT_BOTTOM_LOCKED;
ash::ShelfAutoHideBehavior auto_hide_ = ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN;
size_t alignment_change_count_ = 0;
size_t auto_hide_change_count_ = 0;
ash::mojom::ShelfObserverAssociatedPtr observer_;
mojo::Binding<ash::mojom::ShelfController> binding_;
DISALLOW_COPY_AND_ASSIGN(TestShelfController);
};
// A test ChromeLauncherControllerImpl sublcass that uses TestShelfController.
class TestChromeLauncherControllerImpl : public ChromeLauncherControllerImpl {
public:
TestChromeLauncherControllerImpl(Profile* profile, ash::ShelfModel* model)
: ChromeLauncherControllerImpl(profile, model) {}
// ChromeLauncherControllerImpl:
using ChromeLauncherControllerImpl::ReleaseProfile;
bool ConnectToShelfController() override {
// Set the shelf controller pointer to a test instance; this is run in init.
if (!shelf_controller_.is_bound())
shelf_controller_ = test_shelf_controller_.CreateInterfacePtrAndBind();
return true;
}
TestShelfController* test_shelf_controller() {
return &test_shelf_controller_;
}
private:
TestShelfController test_shelf_controller_;
DISALLOW_COPY_AND_ASSIGN(TestChromeLauncherControllerImpl);
};
using ChromeLauncherControllerImplPrefTest = BrowserWithTestWindowTest;
// Tests that shelf profile preferences are loaded on login.
TEST_F(ChromeLauncherControllerImplPrefTest, PrefsLoadedOnLogin) {
PrefService* prefs = profile()->GetTestingPrefService();
prefs->SetString(prefs::kShelfAlignmentLocal, "Left");
prefs->SetString(prefs::kShelfAlignment, "Left");
prefs->SetString(prefs::kShelfAutoHideBehaviorLocal, "Always");
prefs->SetString(prefs::kShelfAutoHideBehavior, "Always");
ash::ShelfModel* model = ash::Shell::Get()->shelf_controller()->model();
TestChromeLauncherControllerImpl test_launcher_controller(profile(), model);
test_launcher_controller.Init();
// Simulate login for the test controller.
test_launcher_controller.ReleaseProfile();
test_launcher_controller.AttachProfile(profile());
base::RunLoop().RunUntilIdle();
TestShelfController* shelf_controller =
test_launcher_controller.test_shelf_controller();
ASSERT_TRUE(shelf_controller);
EXPECT_EQ(ash::SHELF_ALIGNMENT_LEFT, shelf_controller->alignment());
EXPECT_EQ(1u, shelf_controller->alignment_change_count());
EXPECT_EQ(ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS,
shelf_controller->auto_hide());
EXPECT_EQ(1u, shelf_controller->auto_hide_change_count());
}
// Tests that the shelf controller's changes are not wastefully echoed back.
TEST_F(ChromeLauncherControllerImplPrefTest, DoNotEchoShelfControllerChanges) {
ash::ShelfModel* model = ash::Shell::Get()->shelf_controller()->model();
TestChromeLauncherControllerImpl test_launcher_controller(profile(), model);
test_launcher_controller.Init();
// Simulate login for the test controller.
test_launcher_controller.ReleaseProfile();
test_launcher_controller.AttachProfile(profile());
base::RunLoop().RunUntilIdle();
TestShelfController* shelf_controller =
test_launcher_controller.test_shelf_controller();
ASSERT_TRUE(shelf_controller);
EXPECT_EQ(ash::SHELF_ALIGNMENT_BOTTOM, shelf_controller->alignment());
EXPECT_EQ(1u, shelf_controller->alignment_change_count());
EXPECT_EQ(ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER, shelf_controller->auto_hide());
EXPECT_EQ(1u, shelf_controller->auto_hide_change_count());
// Changing settings via the shelf controller causes the launcher controller
// to update profile prefs. The launcher controller's prefs observation should
// not cause those same changes to be echoed back to the shelf controller.
int64_t display_id = display::Screen::GetScreen()->GetPrimaryDisplay().id();
shelf_controller->SetAlignment(ash::SHELF_ALIGNMENT_LEFT, display_id);
EXPECT_EQ(2u, shelf_controller->alignment_change_count());
shelf_controller->SetAutoHideBehavior(ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS,
display_id);
EXPECT_EQ(2u, shelf_controller->auto_hide_change_count());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(ash::SHELF_ALIGNMENT_LEFT, shelf_controller->alignment());
EXPECT_EQ(2u, shelf_controller->alignment_change_count());
EXPECT_EQ(ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS,
shelf_controller->auto_hide());
EXPECT_EQ(2u, shelf_controller->auto_hide_change_count());
PrefService* prefs = profile()->GetTestingPrefService();
EXPECT_EQ("Left", prefs->GetString(prefs::kShelfAlignmentLocal));
EXPECT_EQ("Left", prefs->GetString(prefs::kShelfAlignment));
EXPECT_EQ("Always", prefs->GetString(prefs::kShelfAutoHideBehaviorLocal));
EXPECT_EQ("Always", prefs->GetString(prefs::kShelfAutoHideBehavior));
}