blob: 4b3190b56537d63c21428e70c9ed0f590793525a [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/common/ash_switches.h"
#include "ash/common/shelf/shelf_model.h"
#include "ash/common/shelf/shelf_model_observer.h"
#include "ash/common/wm/maximize_mode/maximize_mode_controller.h"
#include "ash/common/wm_shell.h"
#include "ash/display/screen_orientation_controller_chromeos.h"
#include "ash/shell.h"
#include "ash/test/ash_test_helper.h"
#include "ash/test/test_session_state_delegate.h"
#include "ash/test/test_shell_delegate.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/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/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/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_application_menu_item_model.h"
#include "chrome/browser/ui/ash/launcher/launcher_controller_helper.h"
#include "chrome/browser/ui/ash/launcher/launcher_item_controller.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/common/app.mojom.h"
#include "components/arc/test/fake_app_instance.h"
#include "components/exo/shell_surface.h"
#include "components/signin/core/account_id/account_id.h"
#include "components/sync/api/fake_sync_change_processor.h"
#include "components/sync/api/sync_error_factory_mock.h"
#include "components/sync/protocol/sync.pb.h"
#include "components/syncable_prefs/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_tree_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/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()
: added_(0),
removed_(0),
changed_(0) {
}
~TestShelfModelObserver() override {}
// Overridden from ash::ShelfModelObserver:
void ShelfItemAdded(int index) override {
++added_;
last_index_ = index;
}
void ShelfItemRemoved(int index, ash::ShelfID id) 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 OnSetShelfItemDelegate(ash::ShelfID id,
ash::ShelfItemDelegate* item_delegate) override {}
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_;
int removed_;
int changed_;
int last_index_;
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;
}
void SetCurrentUser(Profile* profile) override {
// We can ignore this for now.
}
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 LauncherItemController {
public:
TestV2AppLauncherItemController(const std::string& app_id,
ChromeLauncherController* controller)
: LauncherItemController(LauncherItemController::TYPE_APP,
app_id,
"",
controller) {}
~TestV2AppLauncherItemController() override {}
// Override for LauncherItemController:
bool IsOpen() const override { return true; }
bool IsVisible() const override { return true; }
void Launch(ash::LaunchSource source, int event_flags) override {}
ash::ShelfItemDelegate::PerformedAction Activate(
ash::LaunchSource source) override {
return kExistingWindowActivated;
}
void Close() override {}
ash::ShelfItemDelegate::PerformedAction ItemSelected(
const ui::Event& event) override {
return kExistingWindowActivated;
}
base::string16 GetTitle() override { return base::string16(); }
ChromeLauncherAppMenuItems GetApplicationList(int event_flags) override {
ChromeLauncherAppMenuItems items;
items.push_back(
new ChromeLauncherAppMenuItem(base::string16(), NULL, false));
items.push_back(
new ChromeLauncherAppMenuItem(base::string16(), NULL, false));
return items;
}
ash::ShelfMenuModel* CreateApplicationMenu(int event_flags) override {
return NULL;
}
bool IsDraggable() override { return false; }
bool CanPin() const override {
return GetPinnableForAppID(app_id(), launcher_controller()->GetProfile()) ==
AppListControllerDelegate::PIN_EDITABLE;
}
bool ShouldShowTooltip() override { return false; }
private:
DISALLOW_COPY_AND_ASSIGN(TestV2AppLauncherItemController);
};
} // namespace
class ChromeLauncherControllerImplTest : public BrowserWithTestWindowTest {
protected:
ChromeLauncherControllerImplTest()
: BrowserWithTestWindowTest(Browser::TYPE_TABBED, false),
test_controller_(NULL),
extension_service_(NULL) {}
~ChromeLauncherControllerImplTest() override {}
void SetUp() override {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
command_line->AppendSwitch(switches::kUseFirstDisplayAsInternal);
command_line->AppendSwitch(ash::switches::kAshEnableTouchViewTesting);
app_list::AppListSyncableServiceFactory::SetUseInTesting();
BrowserWithTestWindowTest::SetUp();
if (!profile_manager_) {
profile_manager_.reset(
new TestingProfileManager(TestingBrowserProcess::GetGlobal()));
ASSERT_TRUE(profile_manager_->SetUp());
}
model_.reset(new ash::ShelfModel);
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");
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());
app_service_ =
app_list::AppListSyncableServiceFactory::GetForProfile(profile());
StartAppSyncService(syncer::SyncDataList());
std::string 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->Append(new base::StringValue("*://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);
arc_support_host_ = Extension::Create(base::FilePath(), Manifest::UNPACKED,
manifest, Extension::NO_FLAGS,
ArcSupportHost::kHostAppId, &error);
}
// Creates a running V2 app (not pinned) of type |app_id|.
virtual void CreateRunningV2App(const std::string& app_id) {
DCHECK(!test_controller_);
ash::ShelfID id =
launcher_controller_->CreateAppShortcutLauncherItemWithType(
app_id,
model_->item_count(),
ash::TYPE_PLATFORM_APP);
DCHECK(id);
// Change the created launcher controller into a V2 app controller.
test_controller_ = new TestV2AppLauncherItemController(app_id,
launcher_controller_.get());
launcher_controller_->SetItemController(id, test_controller_);
}
// 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());
// 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, extension4_->id());
InsertAddPinChange(user_a, 4, extension5_->id());
InsertAddPinChange(user_a, 5, extension6_->id());
InsertAddPinChange(user_a, 6, extension_misc::kChromeAppId);
// Set user b preferences.
InsertAddPinChange(user_b, 0, extension7_->id());
InsertAddPinChange(user_b, 1, extension8_->id());
InsertAddPinChange(user_b, 2, extension_misc::kChromeAppId);
}
void TearDown() override {
arc_test_.TearDown();
model_->RemoveObserver(model_observer_.get());
model_observer_.reset();
launcher_controller_.reset();
// |model_| must be deleted after |launch_controller_|, because
// |launch_controller_| has a map of pointers to the data held by |model_|.
model_.reset();
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);
}
void InitLauncherController() {
AddAppListLauncherItem();
launcher_controller_.reset(
new ChromeLauncherControllerImpl(profile(), model_.get()));
launcher_controller_->Init();
}
void InitLauncherControllerWithBrowser() {
InitLauncherController();
chrome::NewTab(browser());
browser()->window()->Show();
}
void RecreateChromeLauncher() {
// Destroy controller first if it exists.
launcher_controller_.reset();
model_.reset(new ash::ShelfModel);
AddAppListLauncherItem();
launcher_controller_.reset(
ChromeLauncherControllerImpl::CreateInstance(profile(), model_.get()));
launcher_controller_->Init();
}
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); }
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(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 currently configured app launchers from the controller.
void GetAppLaunchers(ChromeLauncherControllerImpl* controller,
std::vector<std::string>* launchers) {
launchers->clear();
for (ash::ShelfItems::const_iterator iter(model_->items().begin());
iter != model_->items().end(); ++iter) {
ChromeLauncherControllerImpl::IDToItemControllerMap::const_iterator entry(
controller->id_to_item_controller_map_.find(iter->id));
if (iter->type == ash::TYPE_APP_SHORTCUT &&
entry != controller->id_to_item_controller_map_.end()) {
launchers->push_back(entry->second->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_PLATFORM_APP:
result += "*";
// FALLTHROUGH
case ash::TYPE_WINDOWED_APP: {
const std::string& app =
launcher_controller_->GetAppIDForShelfID(model_->items()[i].id);
if (app == extension1_->id()) {
result += "app1";
EXPECT_FALSE(
launcher_controller_->IsAppPinned(extension1_->id()));
} else if (app == extension2_->id()) {
result += "app2";
EXPECT_FALSE(
launcher_controller_->IsAppPinned(extension2_->id()));
} else if (app == extension3_->id()) {
result += "app3";
EXPECT_FALSE(
launcher_controller_->IsAppPinned(extension3_->id()));
} else if (app == extension4_->id()) {
result += "app4";
EXPECT_FALSE(
launcher_controller_->IsAppPinned(extension4_->id()));
} else if (app == extension5_->id()) {
result += "app5";
EXPECT_FALSE(
launcher_controller_->IsAppPinned(extension5_->id()));
} else if (app == extension6_->id()) {
result += "app6";
EXPECT_FALSE(
launcher_controller_->IsAppPinned(extension6_->id()));
} else if (app == extension7_->id()) {
result += "app7";
EXPECT_FALSE(
launcher_controller_->IsAppPinned(extension7_->id()));
} else if (app == extension8_->id()) {
result += "app8";
EXPECT_FALSE(
launcher_controller_->IsAppPinned(extension8_->id()));
} else {
result += "unknown";
}
break;
}
case ash::TYPE_APP_SHORTCUT: {
const std::string& app =
launcher_controller_->GetAppIDForShelfID(model_->items()[i].id);
if (app == extension1_->id()) {
result += "App1";
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
} else if (app == extension2_->id()) {
result += "App2";
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension2_->id()));
} else if (app == extension3_->id()) {
result += "App3";
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension3_->id()));
} else if (app == extension4_->id()) {
result += "App4";
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension4_->id()));
} else if (app == extension5_->id()) {
result += "App5";
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension5_->id()));
} else if (app == extension6_->id()) {
result += "App6";
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension6_->id()));
} else if (app == extension7_->id()) {
result += "App7";
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension7_->id()));
} else if (app == extension8_->id()) {
result += "App8";
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension8_->id()));
} else if (app == arc_support_host_->id()) {
result += "Play Store";
EXPECT_TRUE(
launcher_controller_->IsAppPinned(arc_support_host_->id()));
} 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 UninstallArcApps() {
arc_test_.app_instance()->RefreshAppList();
arc_test_.app_instance()->SendRefreshAppList(
std::vector<arc::mojom::AppInfo>());
}
void EnableArc(bool enable) {
enable ? arc_test_.arc_auth_service()->EnableArc()
: arc_test_.arc_auth_service()->DisableArc();
arc_test_.arc_auth_service()->OnSyncedPrefChanged(prefs::kArcEnabled,
false);
base::RunLoop().RunUntilIdle();
}
void EnableTabletMode(bool enable) {
ash::MaximizeModeController* controller =
ash::WmShell::Get()->maximize_mode_controller();
controller->EnableMaximizeModeWindowManager(enable);
}
void ValidateArcState(bool arc_enabled,
bool arc_managed,
arc::ArcAuthService::State state,
const std::string& pin_status) {
EXPECT_EQ(arc_managed, arc_test_.arc_auth_service()->IsArcManaged());
EXPECT_EQ(arc_enabled, arc_test_.arc_auth_service()->IsArcEnabled());
EXPECT_EQ(state, arc_test_.arc_auth_service()->state());
EXPECT_EQ(pin_status, GetPinnedAppStatus());
}
// Creates app window and set optional Arc application id.
views::Widget* CreateArcWindow(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);
}
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> 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> arc_support_host_;
ArcAppTest arc_test_;
bool auto_start_arc_test_ = false;
std::unique_ptr<ChromeLauncherControllerImpl> launcher_controller_;
std::unique_ptr<TestShelfModelObserver> model_observer_;
std::unique_ptr<ash::ShelfModel> model_;
std::unique_ptr<TestingProfileManager> profile_manager_;
// |item_delegate_manager_| owns |test_controller_|.
LauncherItemController* test_controller_;
ExtensionService* extension_service_;
app_list::AppListSyncableService* app_service_;
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 {
protected:
ChromeLauncherControllerImplWithArcTest() { auto_start_arc_test_ = true; }
~ChromeLauncherControllerImplWithArcTest() override {}
private:
DISALLOW_COPY_AND_ASSIGN(ChromeLauncherControllerImplWithArcTest);
};
// 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) {
// Create a window.
native_window_.reset(new aura::Window(NULL));
native_window_->set_id(0);
native_window_->SetType(ui::wm::WINDOW_TYPE_POPUP);
native_window_->Init(ui::LAYER_TEXTURED);
native_window_->Show();
aura::client::ParentWindowWithContext(native_window_.get(),
ash::Shell::GetPrimaryRootWindow(),
gfx::Rect(10, 10, 20, 30));
Browser::CreateParams params = Browser::CreateParams::CreateForApp(
kCrxAppPrefix + app_name, true /* trusted_source */, gfx::Rect(),
profile);
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(); }
// TestBrowserWindow override:
gfx::NativeWindow GetNativeWindow() const override {
return native_window_.get();
}
private:
// The associated browser with this app.
std::unique_ptr<Browser> browser_;
// The native window we use.
std::unique_ptr<aura::Window> native_window_;
DISALLOW_COPY_AND_ASSIGN(V1App);
};
// A V2 application which gets created with an |extension| and for a |profile|.
// Upon destruction it will properly close the application.
class V2App {
public:
V2App(Profile* profile, const extensions::Extension* extension)
: creator_web_contents_(
content::WebContentsTester::CreateTestWebContents(profile,
nullptr)) {
window_ = new extensions::AppWindow(profile, new ChromeAppDelegate(true),
extension);
extensions::AppWindow::CreateParams params =
extensions::AppWindow::CreateParams();
// 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.
ash::test::AshTestHelper::GetTestSessionStateDelegate()
->set_logged_in_users(2);
shell_delegate_ = static_cast<ash::test::TestShellDelegate*>(
ash::WmShell::Get()->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));
ash::test::AshTestHelper::GetTestSessionStateDelegate()->AddUser(
account_id);
// Add a user to the fake user manager.
ash::test::AshTestHelper::GetTestSessionStateDelegate()->AddUser(
account_id);
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) {
ash::test::AshTestHelper::GetTestSessionStateDelegate()->SwitchActiveUser(
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(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);
// Create a new launcher controller helper and assign it to the launcher so
// that this app gets properly detected.
// TODO(skuhne): Create a more intelligent launcher controller helper that
// is able to detect all running apps properly.
TestLauncherControllerHelper* helper = new TestLauncherControllerHelper;
helper->SetAppID(v1_app->browser()->tab_strip_model()->GetWebContentsAt(0),
app_name);
SetLauncherControllerHelper(helper);
NavigateAndCommitActiveTabWithTitle(
v1_app->browser(), GURL(url), ASCIIToUTF16(""));
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 {
protected:
ChromeLauncherControllerImplMultiProfileWithArcTest() {
auto_start_arc_test_ = true;
}
~ChromeLauncherControllerImplMultiProfileWithArcTest() override {}
private:
DISALLOW_COPY_AND_ASSIGN(ChromeLauncherControllerImplMultiProfileWithArcTest);
};
TEST_F(ChromeLauncherControllerImplTest, DefaultApps) {
InitLauncherController();
// 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_F(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);
launcher_controller_.reset();
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);
RecreateChromeLauncher();
// 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);
launcher_controller_.reset();
ResetPinModel();
SendPinChanges(syncer::SyncChangeList(), true);
StopAppSyncService();
EXPECT_EQ(0U, app_service_->sync_items().size());
// Move back to Arc-disabled platform.
EnableArc(false);
StartAppSyncService(copy_sync_list);
RecreateChromeLauncher();
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());
EnableArc(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.DeepCopy());
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);
// 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);
// 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);
// 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());
}
// Check that simple locking of an application will 'create' a launcher item.
TEST_F(ChromeLauncherControllerImplTest, CheckLockApps) {
InitLauncherController();
// 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_->IsWindowedAppInLauncher(extension1_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension2_->id()));
EXPECT_FALSE(
launcher_controller_->IsWindowedAppInLauncher(extension2_->id()));
launcher_controller_->LockV1AppWithID(extension1_->id());
EXPECT_EQ(3, model_->item_count());
EXPECT_EQ(ash::TYPE_WINDOWED_APP, model_->items()[2].type);
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_TRUE(launcher_controller_->IsWindowedAppInLauncher(extension1_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension2_->id()));
EXPECT_FALSE(
launcher_controller_->IsWindowedAppInLauncher(extension2_->id()));
launcher_controller_->UnlockV1AppWithID(extension1_->id());
EXPECT_EQ(2, model_->item_count());
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(
launcher_controller_->IsWindowedAppInLauncher(extension1_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension2_->id()));
EXPECT_FALSE(
launcher_controller_->IsWindowedAppInLauncher(extension2_->id()));
}
// Check that multiple locks of an application will be properly handled.
TEST_F(ChromeLauncherControllerImplTest, CheckMultiLockApps) {
InitLauncherController();
// 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_->IsWindowedAppInLauncher(extension1_->id()));
for (int i = 0; i < 2; i++) {
launcher_controller_->LockV1AppWithID(extension1_->id());
EXPECT_EQ(3, model_->item_count());
EXPECT_EQ(ash::TYPE_WINDOWED_APP, model_->items()[2].type);
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_TRUE(launcher_controller_->IsWindowedAppInLauncher(
extension1_->id()));
}
launcher_controller_->UnlockV1AppWithID(extension1_->id());
EXPECT_EQ(3, model_->item_count());
EXPECT_EQ(ash::TYPE_WINDOWED_APP, model_->items()[2].type);
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_TRUE(launcher_controller_->IsWindowedAppInLauncher(extension1_->id()));
launcher_controller_->UnlockV1AppWithID(extension1_->id());
EXPECT_EQ(2, model_->item_count());
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(
launcher_controller_->IsWindowedAppInLauncher(extension1_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension2_->id()));
EXPECT_FALSE(
launcher_controller_->IsWindowedAppInLauncher(extension1_->id()));
}
// Check that already pinned items are not effected by locks.
TEST_F(ChromeLauncherControllerImplTest, CheckAlreadyPinnedLockApps) {
InitLauncherController();
// 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_->IsWindowedAppInLauncher(extension1_->id()));
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
launcher_controller_->PinAppWithID(extension1_->id());
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_EQ(3, model_->item_count());
EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_->items()[2].type);
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(
launcher_controller_->IsWindowedAppInLauncher(extension1_->id()));
launcher_controller_->LockV1AppWithID(extension1_->id());
EXPECT_EQ(3, model_->item_count());
EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_->items()[2].type);
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(
launcher_controller_->IsWindowedAppInLauncher(extension1_->id()));
launcher_controller_->UnlockV1AppWithID(extension1_->id());
EXPECT_EQ(3, model_->item_count());
EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_->items()[2].type);
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(
launcher_controller_->IsWindowedAppInLauncher(extension1_->id()));
launcher_controller_->UnpinAppWithID(extension1_->id());
EXPECT_EQ(2, model_->item_count());
}
// Check that already pinned items which get locked stay after unpinning.
TEST_F(ChromeLauncherControllerImplTest, CheckPinnedAppsStayAfterUnlock) {
InitLauncherController();
// 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_->IsWindowedAppInLauncher(extension1_->id()));
launcher_controller_->PinAppWithID(extension1_->id());
EXPECT_EQ(3, model_->item_count());
EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_->items()[2].type);
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(
launcher_controller_->IsWindowedAppInLauncher(extension1_->id()));
launcher_controller_->LockV1AppWithID(extension1_->id());
EXPECT_EQ(3, model_->item_count());
EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_->items()[2].type);
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(
launcher_controller_->IsWindowedAppInLauncher(extension1_->id()));
launcher_controller_->UnpinAppWithID(extension1_->id());
EXPECT_EQ(3, model_->item_count());
EXPECT_EQ(ash::TYPE_WINDOWED_APP, model_->items()[2].type);
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_TRUE(launcher_controller_->IsWindowedAppInLauncher(extension1_->id()));
launcher_controller_->UnlockV1AppWithID(extension1_->id());
EXPECT_EQ(2, model_->item_count());
}
// Check that running applications wich are not pinned get properly restored
// upon user change.
TEST_F(ChromeLauncherControllerImplTest, CheckRunningAppOrder) {
InitLauncherController();
// Model should only contain the browser shortcut and app list items.
EXPECT_EQ(2, model_->item_count());
// Add a few running applications.
launcher_controller_->LockV1AppWithID(extension1_->id());
launcher_controller_->LockV1AppWithID(extension2_->id());
launcher_controller_->LockV1AppWithID(extension3_->id());
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_->UnlockV1AppWithID(extension1_->id());
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_->UnlockV1AppWithID(extension2_->id());
RestoreUnpinnedRunningApplicationOrder(current_account_id);
EXPECT_EQ("AppList, Chrome, app3", GetPinnedAppStatus());
launcher_controller_->UnlockV1AppWithID(extension3_->id());
RestoreUnpinnedRunningApplicationOrder(current_account_id);
EXPECT_EQ("AppList, Chrome", GetPinnedAppStatus());
}
TEST_F(ChromeLauncherControllerImplWithArcTest, ArcDeferredLaunch) {
RecreateChromeLauncher();
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 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);
SendListOfArcApps();
arc_test_.StopArcInstance();
EXPECT_EQ(0, launcher_controller_->GetShelfIDForAppID(arc_app_id1));
EXPECT_EQ(0, launcher_controller_->GetShelfIDForAppID(arc_app_id2));
EXPECT_EQ(0, launcher_controller_->GetShelfIDForAppID(arc_app_id3));
arc::LaunchApp(profile(), arc_app_id1);
arc::LaunchApp(profile(), arc_app_id1);
arc::LaunchApp(profile(), arc_app_id2);
arc::LaunchApp(profile(), arc_app_id3);
arc::LaunchApp(profile(), arc_app_id3);
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);
EXPECT_NE(0, shelf_id_app_1);
EXPECT_NE(0, shelf_id_app_2);
EXPECT_NE(0, shelf_id_app_3);
// We activated arc_app_id1 twice but expect one close for item controller
// stops launching request.
LauncherItemController* item_controller =
launcher_controller_->GetLauncherItemController(shelf_id_app_1);
ASSERT_NE(nullptr, item_controller);
item_controller->Close();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, 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();
EXPECT_EQ(0, launcher_controller_->GetShelfIDForAppID(arc_app_id1));
EXPECT_EQ(0, launcher_controller_->GetShelfIDForAppID(arc_app_id2));
EXPECT_EQ(0, launcher_controller_->GetShelfIDForAppID(arc_app_id3));
ASSERT_EQ(2U, arc_test_.app_instance()->launch_requests().size());
const arc::FakeAppInstance::Request* request1 =
arc_test_.app_instance()->launch_requests()[0];
const arc::FakeAppInstance::Request* request2 =
arc_test_.app_instance()->launch_requests()[1];
EXPECT_TRUE((request1->IsForApp(app2) && request2->IsForApp(app3)) ||
(request1->IsForApp(app3) && request2->IsForApp(app2)));
}
TEST_F(ChromeLauncherControllerImplMultiProfileWithArcTest, ArcMultiUser) {
SendListOfArcApps();
InitLauncherController();
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]);
EXPECT_NE(0, 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]);
EXPECT_NE(0, launcher_controller_->GetShelfIDForAppID(arc_app_id2));
launcher_controller_->SetProfileForTest(profile2);
SwitchActiveUser(account_id2);
EXPECT_EQ(0, launcher_controller_->GetShelfIDForAppID(arc_app_id1));
EXPECT_EQ(0, 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]);
EXPECT_EQ(0, launcher_controller_->GetShelfIDForAppID(arc_app_id3));
arc_window2->CloseNow();
arc_test_.app_instance()->SendTaskDestroyed(2);
launcher_controller_->SetProfileForTest(profile());
SwitchActiveUser(account_id);
EXPECT_NE(0, launcher_controller_->GetShelfIDForAppID(arc_app_id1));
EXPECT_EQ(0, launcher_controller_->GetShelfIDForAppID(arc_app_id2));
EXPECT_NE(0, launcher_controller_->GetShelfIDForAppID(arc_app_id3));
// Close active window to let test passes.
arc_window1->CloseNow();
arc_window3->CloseNow();
}
TEST_F(ChromeLauncherControllerImplWithArcTest, ArcRunningApp) {
InitLauncherController();
const std::string arc_app_id = ArcAppTest::GetAppId(arc_test_.fake_apps()[0]);
SendListOfArcApps();
EXPECT_EQ(0, 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]);
EXPECT_NE(0, launcher_controller_->GetShelfIDForAppID(arc_app_id));
CreateArcWindow(window_app_id2);
arc_test_.app_instance()->SendTaskCreated(2, arc_test_.fake_apps()[0]);
EXPECT_NE(0, launcher_controller_->GetShelfIDForAppID(arc_app_id));
arc_test_.app_instance()->SendTaskDestroyed(1);
EXPECT_NE(0, launcher_controller_->GetShelfIDForAppID(arc_app_id));
arc_test_.app_instance()->SendTaskDestroyed(2);
EXPECT_EQ(0, 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]);
EXPECT_NE(0, launcher_controller_->GetShelfIDForAppID(arc_app_id));
arc_test_.StopArcInstance();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, launcher_controller_->GetShelfIDForAppID(arc_app_id));
}
// Test race creation/deletion of Arc app.
// TODO (khmel): Remove after moving everything to wayland protocol.
TEST_F(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(0, launcher_controller_->GetShelfIDForAppID(arc_app_id1));
ASSERT_TRUE(arc_window);
arc_test_.app_instance()->SendTaskCreated(1, arc_test_.fake_apps()[0]);
EXPECT_NE(0, launcher_controller_->GetShelfIDForAppID(arc_app_id1));
arc_test_.app_instance()->SendTaskDestroyed(1);
EXPECT_EQ(0, launcher_controller_->GetShelfIDForAppID(arc_app_id1));
arc_window->Close();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, 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]);
EXPECT_EQ(0, launcher_controller_->GetShelfIDForAppID(arc_app_id2));
arc_window = CreateArcWindow(window_app_id2);
ASSERT_TRUE(arc_window);
EXPECT_NE(0, 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(0, launcher_controller_->GetShelfIDForAppID(arc_app_id2));
arc_test_.app_instance()->SendTaskDestroyed(2);
EXPECT_EQ(0, launcher_controller_->GetShelfIDForAppID(arc_app_id2));
}
TEST_F(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]);
const ash::ShelfID shelf_id =
launcher_controller_->GetShelfIDForAppID(arc_app_id);
EXPECT_NE(0, 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_F(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());
UninstallArcApps();
EXPECT_EQ("AppList, Chrome, App1, App2", GetPinnedAppStatus());
SendListOfArcApps();
EXPECT_EQ("AppList, Chrome, App1, App2", GetPinnedAppStatus());
// Disable/Enable Arc should persist pin state.
launcher_controller_->PinAppWithID(arc_app_id);
EXPECT_EQ("AppList, Chrome, App1, App2, Fake App 0", GetPinnedAppStatus());
arc::ArcAuthService::Get()->Shutdown();
EXPECT_EQ("AppList, Chrome, App1, App2, Fake App 0", GetPinnedAppStatus());
arc::ArcAuthService::Get()->OnPrimaryUserProfilePrepared(profile());
EXPECT_EQ("AppList, Chrome, App1, App2, Fake App 0", GetPinnedAppStatus());
// Opt-Out/Opt-In remove item from the shelf.
EnableArc(false);
EXPECT_EQ("AppList, Chrome, App1, App2", GetPinnedAppStatus());
EnableArc(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_F(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());
EnableArc(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));
EnableArc(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();
SetLauncherControllerHelper(new TestLauncherControllerHelper);
// 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.
Browser::CreateParams params(profile());
std::unique_ptr<Browser> browser(
chrome::CreateBrowserWithAuraTestWindowForParams(nullptr, &params));
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 lock -> pin -> unlock -> unpin does properly transition.
TEST_F(ChromeLauncherControllerImplTest, CheckLockPinUnlockUnpin) {
InitLauncherController();
// 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_->IsWindowedAppInLauncher(extension1_->id()));
launcher_controller_->LockV1AppWithID(extension1_->id());
EXPECT_EQ(3, model_->item_count());
EXPECT_EQ(ash::TYPE_WINDOWED_APP, model_->items()[2].type);
EXPECT_FALSE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_TRUE(launcher_controller_->IsWindowedAppInLauncher(extension1_->id()));
launcher_controller_->PinAppWithID(extension1_->id());
EXPECT_EQ(3, model_->item_count());
EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_->items()[2].type);
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(
launcher_controller_->IsWindowedAppInLauncher(extension1_->id()));
launcher_controller_->UnlockV1AppWithID(extension1_->id());
EXPECT_EQ(3, model_->item_count());
EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_->items()[2].type);
EXPECT_TRUE(launcher_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(
launcher_controller_->IsWindowedAppInLauncher(extension1_->id()));
launcher_controller_->UnpinAppWithID(extension1_->id());
EXPECT_EQ(2, model_->item_count());
}
// Check that a locked (windowed V1 application) will be properly converted
// between locked and pinned when the order gets changed through a profile /
// policy change.
TEST_F(ChromeLauncherControllerImplTest,
RestoreDefaultAndLockedAppsResyncOrder) {
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());
// Add the app as locked app which will add it (un-pinned).
launcher_controller_->LockV1AppWithID(extension2_->id());
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 should convert the locked item into a
// pinned item.
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());
// Going back to a status where there is no requirement for app 2 to be pinned
// should convert it back to locked but not pinned and state. The position
// is determined by the |ShelfModel|'s weight system and since running
// applications are not allowed to be mixed with shortcuts, it should show up
// at the end of the list.
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 and not pinned V2 application will be properly converted
// between locked and pinned 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(extension2_.get());
// No new app icon will be generated.
EXPECT_EQ("AppList, Chrome, App1", GetPinnedAppStatus());
// Add the app as an unpinned but running V2 app.
CreateRunningV2App(extension2_->id());
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 should convert the locked item into a
// pinned item.
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());
// Going back to a status where there is no requirement for app 2 to be pinned
// should convert it back to running V2 app. Since the position is determined
// by the |ShelfModel|'s weight system, it will be after last pinned item.
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.
syncer::SyncChangeList sync_list3;
InsertAddPinChange(&sync_list3, 0, extension3_->id());
SendPinChanges(sync_list3, true);
EXPECT_EQ("AppList, Chrome, App3, *app2", 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, App4, App5, App6, Chrome",
GetPinnedAppStatus());
// Show user 2.
SendPinChanges(user_b, true);
EXPECT_EQ("AppList, App7, App8, Chrome", GetPinnedAppStatus());
// Switch back to 1.
SendPinChanges(user_a, true);
EXPECT_EQ("AppList, App1, App2, App3, App4, App5, App6, Chrome",
GetPinnedAppStatus());
// Switch back to 2.
SendPinChanges(user_b, true);
EXPECT_EQ("AppList, 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 App1 and assume that it is a V2 app.
CreateRunningV2App(extension1_->id());
// Show user 1.
SendPinChanges(user_a, true);
EXPECT_EQ("AppList, App1, App2, App3, App4, App5, App6, Chrome",
GetPinnedAppStatus());
// Show user 2.
SendPinChanges(user_b, true);
EXPECT_EQ("AppList, App7, App8, Chrome, *app1", GetPinnedAppStatus());
// Switch back to 1.
SendPinChanges(user_a, true);
EXPECT_EQ("AppList, App1, App2, App3, App4, App5, App6, Chrome",
GetPinnedAppStatus());
// Switch back to 2.
SendPinChanges(user_b, true);
EXPECT_EQ("AppList, App7, App8, Chrome, *app1", 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 App1 and assume that it is a V2 app.
CreateRunningV2App(extension1_->id());
// Show user 1.
SendPinChanges(user_a, true);
SetShelfChromeIconIndex(5);
EXPECT_EQ("AppList, App1, App2, App3, App4, App5, Chrome, App6",
GetPinnedAppStatus());
// Show user 2.
SendPinChanges(user_b, true);
SetShelfChromeIconIndex(4);
EXPECT_EQ("AppList, App7, App8, Chrome, *app1", GetPinnedAppStatus());
// Switch back to 1.
SendPinChanges(user_a, true);
SetShelfChromeIconIndex(5);
EXPECT_EQ("AppList, App1, App2, App3, App4, App5, Chrome, App6",
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.DeepCopy());
// 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_APP_SHORTCUT, 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_APP_SHORTCUT, model_->items()[1].type);
EXPECT_EQ(ash::TYPE_APP_SHORTCUT, 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.DeepCopy());
EXPECT_EQ(4, model_->item_count());
EXPECT_EQ(ash::TYPE_APP_SHORTCUT, 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_launchers;
std::vector<std::string> actual_launchers;
GetAppLaunchers(launcher_controller_.get(), &actual_launchers);
EXPECT_EQ(expected_launchers, actual_launchers);
// 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_launchers.push_back(extension2_->id());
expected_launchers.push_back(extension4_->id());
GetAppLaunchers(launcher_controller_.get(), &actual_launchers);
EXPECT_EQ(expected_launchers, actual_launchers);
sync_list.clear();
InsertAddPinChange(&sync_list, 2, extension3_->id());
SendPinChanges(sync_list, false);
expected_launchers.insert(expected_launchers.begin() + 1, extension3_->id());
GetAppLaunchers(launcher_controller_.get(), &actual_launchers);
EXPECT_EQ(expected_launchers, actual_launchers);
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_launchers.begin(), expected_launchers.end());
GetAppLaunchers(launcher_controller_.get(), &actual_launchers);
EXPECT_EQ(expected_launchers, actual_launchers);
// Sending legacy sync change without pin info should not affect pin model.
sync_list.clear();
InsertLegacyPinChange(&sync_list, extension4_->id());
SendPinChanges(sync_list, false);
GetAppLaunchers(launcher_controller_.get(), &actual_launchers);
EXPECT_EQ(expected_launchers, actual_launchers);
sync_list.clear();
InsertRemovePinChange(&sync_list, extension4_->id());
SendPinChanges(sync_list, false);
expected_launchers.erase(expected_launchers.begin());
GetAppLaunchers(launcher_controller_.get(), &actual_launchers);
EXPECT_EQ(expected_launchers, actual_launchers);
sync_list.clear();
InsertRemovePinChange(&sync_list, extension3_->id());
InsertRemovePinChange(&sync_list, extension2_->id());
SendPinChanges(sync_list, false);
expected_launchers.clear();
GetAppLaunchers(launcher_controller_.get(), &actual_launchers);
EXPECT_EQ(expected_launchers, actual_launchers);
}
TEST_F(ChromeLauncherControllerImplTest, ImportLegacyPin) {
extension_service_->AddExtension(extension2_.get());
extension_service_->AddExtension(extension3_.get());
extension_service_->AddExtension(extension4_.get());
// Initially pins are imported from legacy pref based model.
base::ListValue value;
InsertPrefValue(&value, 0, extension4_->id());
InsertPrefValue(&value, 1, extension2_->id());
profile()->GetTestingPrefService()->SetManagedPref(prefs::kPinnedLauncherApps,
value.DeepCopy());
InitLauncherController();
EXPECT_EQ("AppList, Chrome, App4, App2", GetPinnedAppStatus());
// At this point changing old pref based model does not affect pin model.
InsertPrefValue(&value, 2, extension3_->id());
profile()->GetTestingPrefService()->SetManagedPref(prefs::kPinnedLauncherApps,
value.DeepCopy());
EXPECT_EQ("AppList, Chrome, App4, App2", GetPinnedAppStatus());
RecreateChromeLauncher();
EXPECT_EQ("AppList, Chrome, App4, App2", 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_launchers;
expected_launchers.push_back(extension1_->id());
expected_launchers.push_back(extension3_->id());
std::vector<std::string> actual_launchers;
GetAppLaunchers(launcher_controller_.get(), &actual_launchers);
EXPECT_EQ(expected_launchers, actual_launchers);
// Install |extension2| and verify it shows up between the other two.
extension_service_->AddExtension(extension2_.get());
expected_launchers.insert(expected_launchers.begin() + 1, extension2_->id());
GetAppLaunchers(launcher_controller_.get(), &actual_launchers);
EXPECT_EQ(expected_launchers, actual_launchers);
}
// Checks the created menus and menu lists for correctness. It uses the given
// |controller| to create the objects for the given |item| and checks the
// found item count against the |expected_items|. The |title| list contains the
// menu titles in the order of their appearance in the menu (not including the
// application name).
bool CheckMenuCreation(ChromeLauncherControllerImpl* controller,
const ash::ShelfItem& item,
size_t expected_items,
base::string16 title[],
bool is_browser) {
ChromeLauncherAppMenuItems items = controller->GetApplicationList(item, 0);
// A new behavior has been added: Only show menus if there is at least one
// item available.
if (expected_items < 1 && is_browser) {
EXPECT_EQ(0u, items.size());
return items.size() == 0;
}
// There should be one item in there: The title.
EXPECT_EQ(expected_items + 1, items.size());
EXPECT_FALSE(items[0]->IsEnabled());
for (size_t i = 0; i < expected_items; i++) {
EXPECT_EQ(title[i], items[1 + i]->title());
// Check that the first real item has a leading separator.
if (i == 1)
EXPECT_TRUE(items[i]->HasLeadingSeparator());
else
EXPECT_FALSE(items[i]->HasLeadingSeparator());
}
std::unique_ptr<ash::ShelfMenuModel> menu(
new LauncherApplicationMenuItemModel(
controller->GetApplicationList(item, 0)));
// The first element in the menu is a spacing separator. On some systems
// (e.g. Windows) such things do not exist. As such we check the existence
// and adjust dynamically.
int first_item = menu->GetTypeAt(0) == ui::MenuModel::TYPE_SEPARATOR ? 1 : 0;
int expected_menu_items = first_item +
(expected_items ? (expected_items + 3) : 2);
EXPECT_EQ(expected_menu_items, menu->GetItemCount());
EXPECT_FALSE(menu->IsEnabledAt(first_item));
if (expected_items) {
EXPECT_EQ(ui::MenuModel::TYPE_SEPARATOR,
menu->GetTypeAt(first_item + 1));
}
return items.size() == expected_items + 1;
}
// 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);
EXPECT_TRUE(CheckMenuCreation(
launcher_controller_.get(), item_browser, 0, NULL, true));
// 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 };
EXPECT_TRUE(CheckMenuCreation(
launcher_controller_.get(), item_browser, 1, one_menu_item, true));
// 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};
EXPECT_TRUE(CheckMenuCreation(
launcher_controller_.get(), item_browser, 2, two_menu_items, true));
// 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());
EXPECT_TRUE(CheckMenuCreation(
launcher_controller_.get(), item_browser, 0, NULL, true));
// 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 };
EXPECT_TRUE(CheckMenuCreation(
launcher_controller_.get(), item_browser, 1, one_menu_item1, true));
// 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) };
EXPECT_TRUE(CheckMenuCreation(
launcher_controller_.get(), item_browser, 1, one_menu_item1, true));
// Switch to the other user and make sure that only that browser window gets
// shown.
SwitchActiveUser(account_id2);
EXPECT_TRUE(CheckMenuCreation(
launcher_controller_.get(), item_browser, 1, one_menu_item2, true));
// Transferred browsers of other users should not show up in the list.
chrome::MultiUserWindowManager::GetInstance()->ShowWindowForUser(
browser()->window()->GetNativeWindow(), account_id2);
EXPECT_TRUE(CheckMenuCreation(
launcher_controller_.get(), item_browser, 1, one_menu_item2, true));
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();
// 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_APP_SHORTCUT, 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_APP_SHORTCUT;
item_gmail.id = gmail_id;
EXPECT_TRUE(CheckMenuCreation(
launcher_controller_.get(), item_gmail, 0, NULL, false));
// 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 };
EXPECT_TRUE(CheckMenuCreation(
launcher_controller_.get(), item_gmail, 1, one_menu_item, false));
// 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};
EXPECT_TRUE(CheckMenuCreation(
launcher_controller_.get(), item_gmail, 2, two_menu_items, false));
// Even though the item is in the V1 app list, it should also be in the
// browser list.
base::string16 browser_menu_item[] = {title3};
EXPECT_TRUE(CheckMenuCreation(
launcher_controller_.get(), item_browser, 1, browser_menu_item, false));
// Test that closing of (all) the item(s) does work (and all menus get
// updated properly).
launcher_controller_->Close(item_gmail.id);
EXPECT_TRUE(CheckMenuCreation(
launcher_controller_.get(), item_gmail, 0, NULL, false));
base::string16 browser_menu_item2[] = { title2 };
EXPECT_TRUE(CheckMenuCreation(
launcher_controller_.get(), item_browser, 1, browser_menu_item2, false));
}
// 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_APP_SHORTCUT, 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_APP_SHORTCUT;
item_gmail.id = gmail_id;
EXPECT_TRUE(CheckMenuCreation(
launcher_controller_.get(), item_gmail, 0, NULL, false));
// 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 };
EXPECT_TRUE(CheckMenuCreation(
launcher_controller_.get(), item_gmail, 1, one_menu_item, false));
// 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.
EXPECT_TRUE(CheckMenuCreation(
launcher_controller_.get(), item_browser, 0, NULL, true));
EXPECT_TRUE(CheckMenuCreation(
launcher_controller_.get(), item_gmail, 0, NULL, false));
// Transfer the browser of the first user - it should still not show up.
chrome::MultiUserWindowManager::GetInstance()->ShowWindowForUser(
browser()->window()->GetNativeWindow(), account_id2);
EXPECT_TRUE(CheckMenuCreation(
launcher_controller_.get(), item_browser, 0, NULL, true));
EXPECT_TRUE(CheckMenuCreation(
launcher_controller_.get(), item_gmail, 0, NULL, false));
}
// 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 create an app for user #1 there should land on
// his own 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);
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_APP_SHORTCUT;
item_gmail.id = gmail_id;
base::string16 two_menu_items[] = {title1, title2};
EXPECT_TRUE(CheckMenuCreation(
launcher_controller_.get(), item_gmail, 2, two_menu_items, false));
EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
// Execute the second item in the list (which shouldn't do anything since that
// item is per definition already the active tab).
{
std::unique_ptr<ash::ShelfMenuModel> menu(
new LauncherApplicationMenuItemModel(
launcher_controller_->GetApplicationList(item_gmail, 0)));
// The first element in the menu is a spacing separator. On some systems
// (e.g. Windows) such things do not exist. As such we check the existence
// and adjust dynamically.
int first_item =
(menu->GetTypeAt(0) == ui::MenuModel::TYPE_SEPARATOR) ? 1 : 0;
menu->ActivatedAt(first_item + 3);
}
EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
// Execute the first item.
{
std::unique_ptr<ash::ShelfMenuModel> menu(
new LauncherApplicationMenuItemModel(
launcher_controller_->GetApplicationList(item_gmail, 0)));
int first_item =
(menu->GetTypeAt(0) == ui::MenuModel::TYPE_SEPARATOR) ? 1 : 0;
menu->ActivatedAt(first_item + 2);
}
// Now the active tab should be the second item.
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_APP_SHORTCUT;
item_gmail.id = gmail_id;
base::string16 two_menu_items[] = {title1, title2};
EXPECT_TRUE(CheckMenuCreation(
launcher_controller_.get(), item_gmail, 2, two_menu_items, false));
int tabs = browser()->tab_strip_model()->count();
// Activate the proper tab through the menu item.
{
ChromeLauncherAppMenuItems items =
launcher_controller_->GetApplicationList(item_gmail, 0);
items[1]->Execute(0);
EXPECT_EQ(tabs, browser()->tab_strip_model()->count());
}
// Delete one tab through the menu item.
{
ChromeLauncherAppMenuItems items =
launcher_controller_->GetApplicationList(item_gmail, 0);
items[1]->Execute(ui::EF_SHIFT_DOWN);
EXPECT_EQ(--tabs, browser()->tab_strip_model()->count());
}
}
// Tests that panels create launcher items correctly
TEST_F(ChromeLauncherControllerImplTest, AppPanels) {
InitLauncherControllerWithBrowser();
// App list and Browser shortcut ShelfItems are added.
EXPECT_EQ(2, model_observer_->added());
EXPECT_EQ(1, model_observer_->changed());
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));
// Test adding an app panel
AppWindowLauncherItemController* app_panel_controller =
new ExtensionAppWindowLauncherItemController(
LauncherItemController::TYPE_APP_PANEL, app_id, "id",
launcher_controller_.get());
ash::ShelfID shelf_id1 = launcher_controller_->CreateAppLauncherItem(
app_panel_controller, app_id, ash::STATUS_RUNNING);
int panel_index = model_observer_->last_index();
EXPECT_EQ(3, model_observer_->added());
EXPECT_EQ(1, model_observer_->changed());
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(0, launcher_controller_->GetShelfIDForAppID(app_id));
// Setting the app image image should not change the panel if it set its icon
app_panel_controller->set_image_set_by_controller(true);
gfx::ImageSkia image;
launcher_controller_->OnAppImageUpdated(app_id, image);
EXPECT_EQ(0, model_observer_->changed());
model_observer_->clear_counts();
// Add a second app panel and verify that it get the same index as the first
// one had, being added to the left of the existing panel.
AppWindowLauncherItemController* app_panel_controller2 =
new ExtensionAppWindowLauncherItemController(
LauncherItemController::TYPE_APP_PANEL, app_id, "id",
launcher_controller_.get());
ash::ShelfID shelf_id2 = launcher_controller_->CreateAppLauncherItem(
app_panel_controller2, app_id, ash::STATUS_RUNNING);
EXPECT_EQ(panel_index, model_observer_->last_index());
EXPECT_EQ(1, model_observer_->added());
model_observer_->clear_counts();
launcher_controller_->CloseLauncherItem(shelf_id2);
launcher_controller_->CloseLauncherItem(shelf_id1);
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_APP_SHORTCUT, 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_APP_SHORTCUT;
item_gmail.id = gmail_id;
EXPECT_EQ(2U, launcher_controller_->GetApplicationList(item_gmail, 0).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_APP_SHORTCUT, 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_APP_SHORTCUT, model_->items()[2].type);
EXPECT_EQ(ash::TYPE_APP_SHORTCUT, 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_APP_SHORTCUT, model_->items()[1].type);
EXPECT_EQ(ash::TYPE_APP_SHORTCUT, model_->items()[2].type);
EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, model_->items()[3].type);
launcher_controller_.reset();
model_.reset(new ash::ShelfModel);
AddAppListLauncherItem();
launcher_controller_.reset(
ChromeLauncherControllerImpl::CreateInstance(profile(), model_.get()));
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_APP_SHORTCUT, model_->items()[1].type);
EXPECT_EQ(ash::TYPE_APP_SHORTCUT, 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_APP_SHORTCUT, 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());
launcher_controller_.reset();
model_.reset(new ash::ShelfModel);
AddAppListLauncherItem();
launcher_controller_.reset(
ChromeLauncherControllerImpl::CreateInstance(profile(), model_.get()));
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_APP_SHORTCUT, 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));
AppWindowLauncherItemController* app_panel_controller3 =
new ExtensionAppWindowLauncherItemController(
LauncherItemController::TYPE_APP_PANEL, app_id3, "id",
launcher_controller_.get());
const ash::ShelfID shelfId3 = launcher_controller_->CreateAppLauncherItem(
app_panel_controller3, 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());
AppWindowLauncherItemController* app_panel_controller2 =
new ExtensionAppWindowLauncherItemController(
LauncherItemController::TYPE_APP_PANEL, app_id2, "id",
launcher_controller_.get());
const ash::ShelfID shelfId2 = launcher_controller_->CreateAppLauncherItem(
app_panel_controller2, 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());
// Test adding an app panel
AppWindowLauncherItemController* app_panel_controller1 =
new ExtensionAppWindowLauncherItemController(
LauncherItemController::TYPE_APP_PANEL, app_id1, "id",
launcher_controller_.get());
const ash::ShelfID shelfId1 = launcher_controller_->CreateAppLauncherItem(
app_panel_controller1, 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_F(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.DeepCopy());
EXPECT_TRUE(launcher_controller_->IsAppPinned(app_id));
EXPECT_EQ(AppListControllerDelegate::PIN_FIXED,
GetPinnableForAppID(app_id, profile()));
}
TEST_F(ChromeLauncherControllerImplWithArcTest, ArcManaged) {
extension_service_->AddExtension(arc_support_host_.get());
// Test enables Arc, so turn it off for initial values.
EnableArc(false);
InitLauncherController();
arc::ArcAuthService::SetShelfDelegateForTesting(launcher_controller_.get());
// Initial run, Arc is not managed and disabled, Play Store pin should be
// available.
ValidateArcState(false, false, arc::ArcAuthService::State::STOPPED,
"AppList, Chrome, Play Store");
// Arc is managed and enabled, Play Store pin should be available.
profile()->GetTestingPrefService()->SetManagedPref(
prefs::kArcEnabled, new base::FundamentalValue(true));
base::RunLoop().RunUntilIdle();
ValidateArcState(true, true, arc::ArcAuthService::State::FETCHING_CODE,
"AppList, Chrome, Play Store");
// Arc is managed and disabled, Play Store pin should not be available.
profile()->GetTestingPrefService()->SetManagedPref(
prefs::kArcEnabled, new base::FundamentalValue(false));
base::RunLoop().RunUntilIdle();
ValidateArcState(false, true, arc::ArcAuthService::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::ArcAuthService::State::STOPPED,
"AppList, Chrome, Play Store");
// Arc is not managed and enabled, Play Store pin should be available.
EnableArc(true);
ValidateArcState(true, false, arc::ArcAuthService::State::FETCHING_CODE,
"AppList, Chrome, Play Store");
// User disables Arc. Arc is not managed and disabled, Play Store pin should
// be automatically removed.
EnableArc(false);
ValidateArcState(false, false, arc::ArcAuthService::State::STOPPED,
"AppList, Chrome");
// Even if re-enable it again, Play Store pin does not appear automatically.
EnableArc(true);
ValidateArcState(true, false, arc::ArcAuthService::State::FETCHING_CODE,
"AppList, Chrome");
}
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::GetInstance()->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);
};
class ChromeLauncherControllerArcDefaultAppsTest
: public ChromeLauncherControllerImplTest {
public:
ChromeLauncherControllerArcDefaultAppsTest() {}
~ChromeLauncherControllerArcDefaultAppsTest() override {}
protected:
void SetUp() override {
ArcDefaultAppList::UseTestAppsDirectory();
ChromeLauncherControllerImplTest::SetUp();
}
private:
DISALLOW_COPY_AND_ASSIGN(ChromeLauncherControllerArcDefaultAppsTest);
};
} // namespace
TEST_F(ChromeLauncherControllerOrientationTest, ArcOrientationLock) {
ASSERT_TRUE(display::Display::HasInternalDisplay());
extension_service_->AddExtension(arc_support_host_.get());
EnableArc(true);
EnableTabletMode(true);
InitLauncherController();
arc::ArcAuthService::SetShelfDelegateForTesting(launcher_controller_.get());
InitApps();
ash::ScreenOrientationController* controller =
ash::Shell::GetInstance()->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_F(ChromeLauncherControllerOrientationTest, CurrentWithLandscapeDisplay) {
ASSERT_TRUE(display::Display::HasInternalDisplay());
extension_service_->AddExtension(arc_support_host_.get());
EnableArc(true);
EnableTabletMode(true);
InitLauncherController();
arc::ArcAuthService::SetShelfDelegateForTesting(launcher_controller_.get());
InitApps();
ash::ScreenOrientationController* controller =
ash::Shell::GetInstance()->screen_orientation_controller();
// Start with portrait.
window_portrait_->Activate();
// Create a arc window to lock the CURRENT orientation.
views::Widget* window_current = CreateArcWindow(window_app_id_current_);
NotifyOnTaskCreated(appinfo_current_, task_id_current_);
EXPECT_TRUE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_90,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
// Re-activating changes the orientation to previously locked orientation.
window_landscape_->Activate();
EXPECT_TRUE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_0,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
window_current->Activate();
EXPECT_TRUE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_90,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
// Exitting and re-entering tablet mode re-locks the orientation.
EnableTabletMode(false);
EXPECT_FALSE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_0,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
EnableTabletMode(true);
EXPECT_TRUE(window_current->IsActive());
EXPECT_TRUE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_90,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
// Manually unlock, and lock again at landscape.
NotifyOnTaskOrientationLockRequested(task_id_current_, OrientationLock::NONE);
window_landscape_->Activate();
EXPECT_TRUE(controller->rotation_locked());
window_current->Activate();
EXPECT_FALSE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_0,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
NotifyOnTaskOrientationLockRequested(task_id_current_,
OrientationLock::CURRENT);
EXPECT_TRUE(controller->rotation_locked());
EXPECT_EQ(display::Display::ROTATE_0,
display::Screen::GetScreen()->GetPrimaryDisplay().rotation());
}
TEST_F(ChromeLauncherControllerArcDefaultAppsTest, DefaultApps) {
arc_test_.SetUp(profile());
InitLauncherController();
ChromeLauncherController::set_instance(launcher_controller_.get());
ArcAppListPrefs* const prefs = arc_test_.arc_app_list_prefs();
EnableArc(false);
EXPECT_FALSE(arc_test_.arc_auth_service()->IsArcEnabled());
ASSERT_TRUE(prefs->GetAppIds().size());
const std::string app_id =
ArcAppTest::GetAppId(arc_test_.fake_default_apps()[0]);
EXPECT_EQ(0, launcher_controller_->GetShelfIDForAppID(app_id));
EXPECT_TRUE(arc::LaunchApp(profile(), app_id));
EXPECT_TRUE(arc_test_.arc_auth_service()->IsArcEnabled());
EXPECT_NE(0, launcher_controller_->GetShelfIDForAppID(app_id));
// Stop Arc again. Shelf item should go away.
EnableArc(false);
EXPECT_EQ(0, launcher_controller_->GetShelfIDForAppID(app_id));
EXPECT_TRUE(arc::LaunchApp(profile(), app_id));
EXPECT_TRUE(arc_test_.arc_auth_service()->IsArcEnabled());
EXPECT_NE(0, 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]);
EXPECT_NE(0, launcher_controller_->GetShelfIDForAppID(app_id));
EXPECT_FALSE(launcher_controller_->GetArcDeferredLauncher()->HasApp(app_id));
}