blob: cde7f22b3e16064bdc360f8d7f7fd70a9bcbd63c [file] [log] [blame]
// Copyright 2016 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 "ash/common/shelf/shelf_delegate.h"
#include "ash/common/wm_shell.h"
#include "ash/wm/window_util.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/ui/app_list/app_list_service.h"
#include "chrome/browser/ui/app_list/app_list_syncable_service.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_utils.h"
#include "chrome/browser/ui/ash/launcher/arc_app_deferred_launcher_controller.h"
#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
#include "chrome/browser/ui/ash/launcher/launcher_item_controller.h"
#include "chromeos/chromeos_switches.h"
#include "content/public/test/browser_test_utils.h"
#include "mojo/common/common_type_converters.h"
#include "ui/events/event_constants.h"
namespace mojo {
template <>
struct TypeConverter<arc::mojom::AppInfoPtr, arc::mojom::AppInfo> {
static arc::mojom::AppInfoPtr Convert(const arc::mojom::AppInfo& app_info) {
return app_info.Clone();
}
};
template <>
struct TypeConverter<arc::mojom::ArcPackageInfoPtr,
arc::mojom::ArcPackageInfo> {
static arc::mojom::ArcPackageInfoPtr Convert(
const arc::mojom::ArcPackageInfo& package_info) {
return package_info.Clone();
}
};
} // namespace mojo
namespace {
const char kTestAppName[] = "Test Arc App";
const char kTestAppName2[] = "Test Arc App 2";
const char kTestAppPackage[] = "test.arc.app.package";
const char kTestAppActivity[] = "test.arc.app.package.activity";
const char kTestAppActivity2[] = "test.arc.gitapp.package.activity2";
constexpr int kAppAnimatedThresholdMs = 100;
std::string GetTestApp1Id() {
return ArcAppListPrefs::GetAppId(kTestAppPackage, kTestAppActivity);
}
std::string GetTestApp2Id() {
return ArcAppListPrefs::GetAppId(kTestAppPackage, kTestAppActivity2);
}
std::vector<arc::mojom::AppInfoPtr> GetTestAppsList(bool multi_app) {
std::vector<arc::mojom::AppInfoPtr> apps;
arc::mojom::AppInfoPtr app(arc::mojom::AppInfo::New());
app->name = kTestAppName;
app->package_name = kTestAppPackage;
app->activity = kTestAppActivity;
app->sticky = false;
apps.push_back(std::move(app));
if (multi_app) {
app = arc::mojom::AppInfo::New();
app->name = kTestAppName2;
app->package_name = kTestAppPackage;
app->activity = kTestAppActivity2;
app->sticky = false;
apps.push_back(std::move(app));
}
return apps;
}
ChromeLauncherController* chrome_controller() {
return ChromeLauncherController::instance();
}
ash::ShelfDelegate* shelf_delegate() {
return ash::WmShell::Get()->shelf_delegate();
}
class AppAnimatedWaiter {
public:
explicit AppAnimatedWaiter(const std::string& app_id) : app_id_(app_id) {}
void Wait() {
const base::TimeDelta threshold =
base::TimeDelta::FromMilliseconds(kAppAnimatedThresholdMs);
ArcAppDeferredLauncherController* controller =
chrome_controller()->GetArcDeferredLauncher();
while (controller->GetActiveTime(app_id_) < threshold) {
base::RunLoop().RunUntilIdle();
}
}
private:
const std::string app_id_;
};
enum TestAction {
TEST_ACTION_START, // Start app on app appears.
TEST_ACTION_EXIT, // Exit Chrome during animation.
TEST_ACTION_CLOSE, // Close item during animation.
};
// Test parameters include TestAction and pin/unpin state.
typedef std::tr1::tuple<TestAction, bool> TestParameter;
TestParameter build_test_parameter[] = {
TestParameter(TEST_ACTION_START, false),
TestParameter(TEST_ACTION_EXIT, false),
TestParameter(TEST_ACTION_CLOSE, false),
TestParameter(TEST_ACTION_START, true),
};
} // namespace
class ArcAppLauncherBrowserTest : public ExtensionBrowserTest {
public:
ArcAppLauncherBrowserTest() {}
~ArcAppLauncherBrowserTest() override {}
protected:
// content::BrowserTestBase:
void SetUpCommandLine(base::CommandLine* command_line) override {
ExtensionBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(chromeos::switches::kEnableArc);
}
void SetUpInProcessBrowserTestFixture() override {
ExtensionBrowserTest::SetUpInProcessBrowserTestFixture();
arc::ArcSessionManager::DisableUIForTesting();
}
void SetUpOnMainThread() override {
arc::ArcSessionManager::Get()->EnableArc();
}
void InstallTestApps(bool multi_app) {
app_host()->OnAppListRefreshed(GetTestAppsList(multi_app));
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
app_prefs()->GetApp(GetTestApp1Id());
ASSERT_TRUE(app_info);
EXPECT_TRUE(app_info->ready);
if (multi_app) {
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info2 =
app_prefs()->GetApp(GetTestApp2Id());
ASSERT_TRUE(app_info2);
EXPECT_TRUE(app_info2->ready);
}
}
void SendPackageAdded(bool package_synced) {
arc::mojom::ArcPackageInfo package_info;
package_info.package_name = kTestAppPackage;
package_info.package_version = 1;
package_info.last_backup_android_id = 1;
package_info.last_backup_time = 1;
package_info.sync = package_synced;
package_info.system = false;
app_host()->OnPackageAdded(arc::mojom::ArcPackageInfo::From(package_info));
base::RunLoop().RunUntilIdle();
}
void SendPackageUpdated(bool multi_app) {
app_host()->OnPackageAppListRefreshed(kTestAppPackage,
GetTestAppsList(multi_app));
}
void SendPackageRemoved() { app_host()->OnPackageRemoved(kTestAppPackage); }
void StartInstance() {
if (arc_session_manager()->profile() != profile())
arc_session_manager()->OnPrimaryUserProfilePrepared(profile());
app_instance_observer()->OnInstanceReady();
}
void StopInstance() {
arc_session_manager()->Shutdown();
app_instance_observer()->OnInstanceClosed();
}
ArcAppListPrefs* app_prefs() { return ArcAppListPrefs::Get(profile()); }
// Returns as AppHost interface in order to access to private implementation
// of the interface.
arc::mojom::AppHost* app_host() { return app_prefs(); }
// Returns as AppInstance observer interface in order to access to private
// implementation of the interface.
arc::InstanceHolder<arc::mojom::AppInstance>::Observer*
app_instance_observer() {
return app_prefs();
}
arc::ArcSessionManager* arc_session_manager() {
return arc::ArcSessionManager::Get();
}
private:
DISALLOW_COPY_AND_ASSIGN(ArcAppLauncherBrowserTest);
};
class ArcAppDeferredLauncherBrowserTest
: public ArcAppLauncherBrowserTest,
public testing::WithParamInterface<TestParameter> {
public:
ArcAppDeferredLauncherBrowserTest() {}
~ArcAppDeferredLauncherBrowserTest() override {}
protected:
bool is_pinned() const { return std::tr1::get<1>(GetParam()); }
TestAction test_action() const { return std::tr1::get<0>(GetParam()); }
private:
DISALLOW_COPY_AND_ASSIGN(ArcAppDeferredLauncherBrowserTest);
};
// This tests simulates normal workflow for starting Arc app in deferred mode.
IN_PROC_BROWSER_TEST_P(ArcAppDeferredLauncherBrowserTest, StartAppDeferred) {
// Install app to remember existing apps.
StartInstance();
InstallTestApps(false);
SendPackageAdded(false);
const std::string app_id = GetTestApp1Id();
if (is_pinned()) {
shelf_delegate()->PinAppWithID(app_id);
EXPECT_TRUE(shelf_delegate()->GetShelfIDForAppID(app_id));
} else {
EXPECT_FALSE(shelf_delegate()->GetShelfIDForAppID(app_id));
}
StopInstance();
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
app_prefs()->GetApp(app_id);
EXPECT_FALSE(app_info);
// Restart instance. App should be taken from prefs but its state is non-ready
// currently.
StartInstance();
app_info = app_prefs()->GetApp(app_id);
ASSERT_TRUE(app_info);
EXPECT_FALSE(app_info->ready);
if (is_pinned())
EXPECT_TRUE(shelf_delegate()->GetShelfIDForAppID(app_id));
else
EXPECT_FALSE(shelf_delegate()->GetShelfIDForAppID(app_id));
// Launching non-ready Arc app creates item on shelf and spinning animation.
arc::LaunchApp(profile(), app_id, ui::EF_LEFT_MOUSE_BUTTON);
EXPECT_TRUE(shelf_delegate()->GetShelfIDForAppID(app_id));
AppAnimatedWaiter(app_id).Wait();
switch (test_action()) {
case TEST_ACTION_START:
// Now simulates that Arc is started and app list is refreshed. This
// should stop animation and delete icon from the shelf.
InstallTestApps(false);
SendPackageAdded(false);
EXPECT_TRUE(chrome_controller()
->GetArcDeferredLauncher()
->GetActiveTime(app_id)
.is_zero());
if (is_pinned())
EXPECT_TRUE(shelf_delegate()->GetShelfIDForAppID(app_id));
else
EXPECT_FALSE(shelf_delegate()->GetShelfIDForAppID(app_id));
break;
case TEST_ACTION_EXIT:
// Just exist Chrome.
break;
case TEST_ACTION_CLOSE:
// Close item during animation.
{
LauncherItemController* controller =
chrome_controller()->GetLauncherItemController(
shelf_delegate()->GetShelfIDForAppID(app_id));
ASSERT_TRUE(controller);
controller->Close();
EXPECT_TRUE(chrome_controller()
->GetArcDeferredLauncher()
->GetActiveTime(app_id)
.is_zero());
if (is_pinned())
EXPECT_TRUE(shelf_delegate()->GetShelfIDForAppID(app_id));
else
EXPECT_FALSE(shelf_delegate()->GetShelfIDForAppID(app_id));
}
break;
}
}
INSTANTIATE_TEST_CASE_P(ArcAppDeferredLauncherBrowserTestInstance,
ArcAppDeferredLauncherBrowserTest,
::testing::ValuesIn(build_test_parameter));
// This tests validates pin state on package update and remove.
IN_PROC_BROWSER_TEST_F(ArcAppLauncherBrowserTest, PinOnPackageUpdateAndRemove) {
StartInstance();
// Make use app list sync service is started. Normally it is started when
// sycing is initialized.
app_list::AppListSyncableServiceFactory::GetForProfile(profile())->GetModel();
InstallTestApps(true);
SendPackageAdded(false);
const std::string app_id1 = GetTestApp1Id();
const std::string app_id2 = GetTestApp2Id();
shelf_delegate()->PinAppWithID(app_id1);
shelf_delegate()->PinAppWithID(app_id2);
const ash::ShelfID shelf_id1_before =
shelf_delegate()->GetShelfIDForAppID(app_id1);
EXPECT_TRUE(shelf_id1_before);
EXPECT_TRUE(shelf_delegate()->GetShelfIDForAppID(app_id2));
// Package contains only one app. App list is not shown for updated package.
SendPackageUpdated(false);
// Second pin should gone.
EXPECT_EQ(shelf_id1_before, shelf_delegate()->GetShelfIDForAppID(app_id1));
EXPECT_FALSE(shelf_delegate()->GetShelfIDForAppID(app_id2));
// Package contains two apps. App list is not shown for updated package.
SendPackageUpdated(true);
// Second pin should not appear.
EXPECT_EQ(shelf_id1_before, shelf_delegate()->GetShelfIDForAppID(app_id1));
EXPECT_FALSE(shelf_delegate()->GetShelfIDForAppID(app_id2));
// Package removed.
SendPackageRemoved();
// No pin is expected.
EXPECT_FALSE(shelf_delegate()->GetShelfIDForAppID(app_id1));
EXPECT_FALSE(shelf_delegate()->GetShelfIDForAppID(app_id2));
}
// This test validates that app list is shown on new package and not shown
// on package update.
IN_PROC_BROWSER_TEST_F(ArcAppLauncherBrowserTest, AppListShown) {
StartInstance();
AppListService* app_list_service = AppListService::Get();
ASSERT_TRUE(app_list_service);
EXPECT_FALSE(app_list_service->IsAppListVisible());
// New package is available. Show app list.
InstallTestApps(false);
SendPackageAdded(true);
EXPECT_TRUE(app_list_service->IsAppListVisible());
app_list_service->DismissAppList();
EXPECT_FALSE(app_list_service->IsAppListVisible());
// Send package update event. App list is not shown.
SendPackageAdded(true);
EXPECT_FALSE(app_list_service->IsAppListVisible());
}
// Test AppListControllerDelegate::IsAppOpen for Arc apps.
IN_PROC_BROWSER_TEST_F(ArcAppLauncherBrowserTest, IsAppOpen) {
StartInstance();
InstallTestApps(false);
SendPackageAdded(true);
const std::string app_id = GetTestApp1Id();
AppListService* service = AppListService::Get();
AppListControllerDelegate* delegate = service->GetControllerDelegate();
EXPECT_FALSE(delegate->IsAppOpen(app_id));
arc::LaunchApp(profile(), app_id, ui::EF_LEFT_MOUSE_BUTTON);
EXPECT_FALSE(delegate->IsAppOpen(app_id));
// Simulate task creation so the app is marked as running/open.
std::unique_ptr<ArcAppListPrefs::AppInfo> info = app_prefs()->GetApp(app_id);
app_host()->OnTaskCreated(0, info->package_name, info->activity, info->name);
EXPECT_TRUE(delegate->IsAppOpen(app_id));
}