Commit 0136849d authored by Andrew Xu's avatar Andrew Xu Committed by Commit Bot

Add PresentationTimeRecorder For Tablet Mode

Add PresentationTimeRecorder to record all tablet mode launcher
gesture interactions, which include:
(1) Swiping from bottom of screen to show launcher
(2) Swiping from top of screen to hide launcher

Test: ash_unittests
Bug: 947105
Change-Id: Ib1d014b1b7141883d9e78035a26cbeeea689c651
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1564880
Commit-Queue: Andrew Xu <andrewxu@chromium.org>
Reviewed-by: default avatarMitsuru Oshima <oshima@chromium.org>
Cr-Commit-Position: refs/heads/master@{#652410}
parent 35be52ae
......@@ -1113,6 +1113,24 @@ void AppListControllerImpl::NotifyAppListTargetVisibilityChanged(bool visible) {
////////////////////////////////////////////////////////////////////////////////
// Private used only:
void AppListControllerImpl::OnHomeLauncherDragStart() {
app_list::AppListView* app_list_view = presenter_.GetView();
DCHECK(app_list_view);
app_list_view->OnHomeLauncherDragStart();
}
void AppListControllerImpl::OnHomeLauncherDragInProgress() {
app_list::AppListView* app_list_view = presenter_.GetView();
DCHECK(app_list_view);
app_list_view->OnHomeLauncherDragInProgress();
}
void AppListControllerImpl::OnHomeLauncherDragEnd() {
app_list::AppListView* app_list_view = presenter_.GetView();
DCHECK(app_list_view);
app_list_view->OnHomeLauncherDragEnd();
}
syncer::StringOrdinal AppListControllerImpl::GetOemFolderPos() {
// Place the OEM folder just after the web store, which should always be
// followed by a pre-installed app (e.g. Search), so the poosition should be
......
......@@ -304,6 +304,11 @@ class ASH_EXPORT AppListControllerImpl
base::Optional<mojom::AppListViewState> recorded_app_list_view_state);
private:
// HomeScreenDelegate:
void OnHomeLauncherDragStart() override;
void OnHomeLauncherDragInProgress() override;
void OnHomeLauncherDragEnd() override;
syncer::StringOrdinal GetOemFolderPos();
std::unique_ptr<app_list::AppListItem> CreateAppListItem(
AppListItemMetadataPtr metadata);
......
......@@ -13,6 +13,9 @@
#include "ash/home_screen/home_launcher_gesture_handler.h"
#include "ash/home_screen/home_screen_controller.h"
#include "ash/keyboard/ash_keyboard_controller.h"
#include "ash/public/cpp/presentation_time_recorder.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
......@@ -21,6 +24,7 @@
#include "base/strings/string16.cc"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "ui/events/test/event_generator.h"
namespace ash {
......@@ -247,6 +251,14 @@ class AppListControllerImplMetricsTest : public AshTestBase {
void SetUp() override {
AshTestBase::SetUp();
controller_ = ash::Shell::Get()->app_list_controller();
ash::PresentationTimeRecorder::SetReportPresentationTimeImmediatelyForTest(
true);
}
void TearDown() override {
ash::PresentationTimeRecorder::SetReportPresentationTimeImmediatelyForTest(
false);
AshTestBase::TearDown();
}
AppListControllerImpl* controller_;
......@@ -309,4 +321,159 @@ TEST_F(AppListControllerImplMetricsTest, LogManyClicksInOneBucket) {
32, 50);
}
// Verifies that the PresentationTimeRecorder works correctly for the home
// launcher gesture drag in tablet mode (https://crbug.com/947105).
TEST_F(AppListControllerImplMetricsTest,
PresentationTimeRecordedForDragInTabletMode) {
// Wait until the construction of TabletModeController finishes.
base::RunLoop().RunUntilIdle();
// Turn on the tablet mode.
Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(true);
EXPECT_TRUE(IsTabletMode());
// Create a window then press the home launcher button. Expect that |w| is
// hidden.
std::unique_ptr<aura::Window> w(
AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400)));
Shell::Get()
->home_screen_controller()
->home_launcher_gesture_handler()
->ShowHomeLauncher(display::Screen::GetScreen()->GetPrimaryDisplay());
EXPECT_FALSE(w->IsVisible());
EXPECT_EQ(mojom::AppListViewState::kFullscreenAllApps,
GetAppListView()->app_list_state());
int delta_y = 1;
gfx::Point start = GetAppListView()
->get_fullscreen_widget_for_test()
->GetWindowBoundsInScreen()
.top_right();
base::TimeTicks timestamp = base::TimeTicks::Now();
// Emulate to drag the launcher downward.
// Send SCROLL_START event. Check the presentation metrics values.
ui::GestureEvent start_event = ui::GestureEvent(
start.x(), start.y(), ui::EF_NONE, timestamp,
ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN, 0, delta_y));
GetAppListView()->OnGestureEvent(&start_event);
histogram_tester_.ExpectTotalCount(
"Apps.StateTransition.Drag.PresentationTime.TabletMode", 0);
histogram_tester_.ExpectTotalCount(
"Apps.StateTransition.Drag.PresentationTime.MaxLatency.TabletMode", 0);
// Send SCROLL_UPDATE event. Check the presentation metrics values.
timestamp += base::TimeDelta::FromMilliseconds(25);
delta_y += 20;
start.Offset(0, 1);
ui::GestureEvent update_event = ui::GestureEvent(
start.x(), start.y(), ui::EF_NONE, timestamp,
ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE, 0, delta_y));
GetAppListView()->OnGestureEvent(&update_event);
histogram_tester_.ExpectTotalCount(
"Apps.StateTransition.Drag.PresentationTime.TabletMode", 1);
histogram_tester_.ExpectTotalCount(
"Apps.StateTransition.Drag.PresentationTime.MaxLatency.TabletMode", 0);
// Send SCROLL_END event. Check the presentation metrics values.
timestamp += base::TimeDelta::FromMilliseconds(25);
start.Offset(0, 1);
ui::GestureEvent end_event =
ui::GestureEvent(start.x(), start.y() + delta_y, ui::EF_NONE, timestamp,
ui::GestureEventDetails(ui::ET_GESTURE_END));
GetAppListView()->OnGestureEvent(&end_event);
histogram_tester_.ExpectTotalCount(
"Apps.StateTransition.Drag.PresentationTime.TabletMode", 1);
histogram_tester_.ExpectTotalCount(
"Apps.StateTransition.Drag.PresentationTime.MaxLatency.TabletMode", 1);
// After the gesture scroll event ends, the window shows.
EXPECT_TRUE(w->IsVisible());
ASSERT_TRUE(IsTabletMode());
}
// One edge case may do harm to the presentation metrics reporter for tablet
// mode: the user may keep pressing on launcher while exiting the tablet mode by
// rotating the lid. In this situation, OnHomeLauncherDragEnd is not triggered.
// It is handled correctly now because the AppListView is always closed after
// exiting the tablet mode. But it still has potential risk to break in future.
// Write this test case for precaution (https://crbug.com/947105).
TEST_F(AppListControllerImplMetricsTest,
PresentationMetricsForTabletNotRecordedInClamshell) {
// Wait until the construction of TabletModeController finishes.
base::RunLoop().RunUntilIdle();
// Turn on the tablet mode.
Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(true);
EXPECT_TRUE(IsTabletMode());
// Create a window then press the home launcher button. Expect that |w| is
// hidden.
std::unique_ptr<aura::Window> w(
AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400)));
Shell::Get()
->home_screen_controller()
->home_launcher_gesture_handler()
->ShowHomeLauncher(display::Screen::GetScreen()->GetPrimaryDisplay());
EXPECT_FALSE(w->IsVisible());
EXPECT_EQ(mojom::AppListViewState::kFullscreenAllApps,
GetAppListView()->app_list_state());
gfx::Point start = GetAppListView()
->get_fullscreen_widget_for_test()
->GetWindowBoundsInScreen()
.top_right();
base::TimeTicks timestamp = base::TimeTicks::Now();
// Emulate to drag the launcher downward.
// Send SCROLL_START event. Check the presentation metrics values.
ui::GestureEvent start_event = ui::GestureEvent(
start.x(), start.y(), ui::EF_NONE, timestamp,
ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN, 0, 1));
GetAppListView()->OnGestureEvent(&start_event);
// Turn off the tablet mode before scrolling is finished.
Shell::Get()->tablet_mode_controller()->EnableTabletModeWindowManager(false);
EXPECT_FALSE(IsTabletMode());
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(GetAppListView());
// Check metrics initial values.
histogram_tester_.ExpectTotalCount(
"Apps.StateTransition.Drag.PresentationTime.TabletMode", 0);
histogram_tester_.ExpectTotalCount(
"Apps.StateTransition.Drag.PresentationTime.MaxLatency.TabletMode", 0);
// Emulate to drag launcher from shelf. Then verifies the following things:
// (1) Metrics values for tablet mode are not recorded.
// (2) Metrics values for clamshell mode are recorded correctly.
gfx::Rect shelf_bounds =
GetPrimaryShelf()->shelf_widget()->GetWindowBoundsInScreen();
shelf_bounds.Intersect(
display::Screen::GetScreen()->GetPrimaryDisplay().bounds());
gfx::Point shelf_center = shelf_bounds.CenterPoint();
gfx::Point target_point =
display::Screen::GetScreen()->GetPrimaryDisplay().bounds().CenterPoint();
ui::test::EventGenerator* generator = GetEventGenerator();
generator->GestureScrollSequence(shelf_center, target_point,
base::TimeDelta::FromMicroseconds(500), 1);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(mojom::AppListViewState::kFullscreenAllApps,
GetAppListView()->app_list_state());
histogram_tester_.ExpectTotalCount(
"Apps.StateTransition.Drag.PresentationTime.TabletMode", 0);
histogram_tester_.ExpectTotalCount(
"Apps.StateTransition.Drag.PresentationTime.MaxLatency.TabletMode", 0);
// AppListView::UpdateYPositionAndOpacity is triggered by
// ShelfLayoutManager::StartGestureDrag and
// ShelfLayoutManager::UpdateGestureDrag. Note that scrolling step of event
// generator is 1. So the expected value is 2.
histogram_tester_.ExpectTotalCount(
"Apps.StateTransition.Drag.PresentationTime.ClamshellMode", 2);
histogram_tester_.ExpectTotalCount(
"Apps.StateTransition.Drag.PresentationTime.MaxLatency.ClamshellMode", 1);
}
} // namespace ash
......@@ -99,13 +99,18 @@ constexpr float kAppListBlurQuality = 0.33f;
// TODO(oshima): Use ui::ScopedAnimationDurationScaleMode instead.
bool short_animations_for_testing;
// Histogram for the app list dragging. The suffix ClamshellMode is added
// in case a similar UI is added to TabletMode in the future.
// Histogram for the app list dragging in clamshell mode.
constexpr char kAppListDragInClamshellHistogram[] =
"Apps.StateTransition.Drag.PresentationTime.ClamshellMode";
constexpr char kAppListDragInClamshellMaxLatencyHistogram[] =
"Apps.StateTransition.Drag.PresentationTime.MaxLatency.ClamshellMode";
// Histogram for the app list dragging in tablet mode.
constexpr char kAppListDragInTabletHistogram[] =
"Apps.StateTransition.Drag.PresentationTime.TabletMode";
constexpr char kAppListDragInTabletMaxLatencyHistogram[] =
"Apps.StateTransition.Drag.PresentationTime.MaxLatency.TabletMode";
// This view forwards the focus to the search box widget by providing it as a
// FocusTraversable when a focus search is provided.
class SearchBoxFocusHost : public views::View {
......@@ -1527,7 +1532,9 @@ gfx::Rect AppListView::GetAppInfoDialogBounds() const {
}
void AppListView::SetIsInDrag(bool is_in_drag) {
if (!is_in_drag)
// In tablet mode, |presentation_time_recorder_| is constructed/reset by
// HomeLauncherGestureHandler.
if (!is_in_drag && !is_tablet_mode_)
presentation_time_recorder_.reset();
if (app_list_state_ == ash::mojom::AppListViewState::kClosed)
......@@ -1536,14 +1543,11 @@ void AppListView::SetIsInDrag(bool is_in_drag) {
if (is_in_drag == is_in_drag_)
return;
if (is_in_drag) {
if (is_in_drag && !is_tablet_mode_) {
DCHECK(!presentation_time_recorder_);
if (!is_tablet_mode_) {
presentation_time_recorder_ =
ash::CreatePresentationTimeHistogramRecorder(
GetWidget()->GetCompositor(), kAppListDragInClamshellHistogram,
kAppListDragInClamshellMaxLatencyHistogram);
}
presentation_time_recorder_ = ash::CreatePresentationTimeHistogramRecorder(
GetWidget()->GetCompositor(), kAppListDragInClamshellHistogram,
kAppListDragInClamshellMaxLatencyHistogram);
}
is_in_drag_ = is_in_drag;
......@@ -1642,6 +1646,22 @@ ui::AnimationMetricsReporter* AppListView::GetStateTransitionMetricsReporter() {
return state_animation_metrics_reporter_.get();
}
void AppListView::OnHomeLauncherDragStart() {
DCHECK(!presentation_time_recorder_);
presentation_time_recorder_ = ash::CreatePresentationTimeHistogramRecorder(
GetWidget()->GetCompositor(), kAppListDragInTabletHistogram,
kAppListDragInTabletMaxLatencyHistogram);
}
void AppListView::OnHomeLauncherDragInProgress() {
DCHECK(presentation_time_recorder_);
presentation_time_recorder_->RequestNext();
}
void AppListView::OnHomeLauncherDragEnd() {
presentation_time_recorder_.reset();
}
void AppListView::OnWindowDestroying(aura::Window* window) {
DCHECK_EQ(fullscreen_widget_->GetNativeView(), window);
window->RemoveObserver(this);
......
......@@ -38,6 +38,8 @@ FORWARD_DECLARE_TEST(AppListControllerImplTest,
CheckAppListViewBoundsWhenVKeyboardEnabled);
FORWARD_DECLARE_TEST(AppListControllerImplTest,
CheckAppListViewBoundsWhenDismissVKeyboard);
FORWARD_DECLARE_TEST(AppListControllerImplMetricsTest,
PresentationTimeRecordedForDragInTabletMode);
}
namespace app_list {
......@@ -254,6 +256,11 @@ class APP_LIST_EXPORT AppListView : public views::WidgetDelegateView,
// Returns a animation metrics reportre for state transition.
ui::AnimationMetricsReporter* GetStateTransitionMetricsReporter();
// Called when drag in tablet mode starts/proceeds/ends.
void OnHomeLauncherDragStart();
void OnHomeLauncherDragInProgress();
void OnHomeLauncherDragEnd();
// WindowObserver overrides:
void OnWindowDestroying(aura::Window* window) override;
void OnWindowBoundsChanged(aura::Window* window,
......@@ -311,6 +318,8 @@ class APP_LIST_EXPORT AppListView : public views::WidgetDelegateView,
CheckAppListViewBoundsWhenVKeyboardEnabled);
FRIEND_TEST_ALL_PREFIXES(ash::AppListControllerImplTest,
CheckAppListViewBoundsWhenDismissVKeyboard);
FRIEND_TEST_ALL_PREFIXES(ash::AppListControllerImplMetricsTest,
PresentationTimeRecordedForDragInTabletMode);
// A widget observer that is responsible for keeping the AppListView state up
// to date on closing.
......
......@@ -173,6 +173,10 @@ aura::Window* GetDividerWindow() {
->GetNativeWindow();
}
HomeScreenDelegate* GetHomeScreenDelegate() {
return Shell::Get()->home_screen_controller()->delegate();
}
} // namespace
// Class which allows us to make modifications to a window, and removes those
......@@ -307,6 +311,10 @@ bool HomeLauncherGestureHandler::OnPressEvent(Mode mode,
mode == Mode::kSlideUpToShow /*showing*/, display_.id());
}
HomeScreenDelegate* home_screen_delegate = GetHomeScreenDelegate();
DCHECK(home_screen_delegate);
home_screen_delegate->OnHomeLauncherDragStart();
UpdateWindows(0.0, /*animate=*/false);
return true;
}
......@@ -323,6 +331,11 @@ bool HomeLauncherGestureHandler::OnScrollEvent(const gfx::Point& location,
last_scroll_y_ = scroll_y;
DCHECK(display_.is_valid());
HomeScreenDelegate* home_screen_delegate = GetHomeScreenDelegate();
DCHECK(home_screen_delegate);
home_screen_delegate->OnHomeLauncherDragInProgress();
UpdateWindows(GetHeightInWorkAreaAsRatio(location, display_.work_area()),
/*animate=*/false);
return true;
......@@ -332,6 +345,14 @@ bool HomeLauncherGestureHandler::OnReleaseEvent(const gfx::Point& location) {
if (IsAnimating())
return false;
// In clamshell mode, AppListView::SetIsInDrag is called explicitly so it
// does not need the notification from HomeLauncherGestureHandler.
if (IsTabletMode()) {
HomeScreenDelegate* home_screen_delegate = GetHomeScreenDelegate();
DCHECK(home_screen_delegate);
home_screen_delegate->OnHomeLauncherDragEnd();
}
if (!IsDragInProgress()) {
if (GetActiveWindow()) {
// |active_window_| may not be nullptr when this release event is
......@@ -354,6 +375,10 @@ void HomeLauncherGestureHandler::Cancel() {
if (!IsDragInProgress())
return;
HomeScreenDelegate* home_screen_delegate = GetHomeScreenDelegate();
DCHECK(home_screen_delegate);
home_screen_delegate->OnHomeLauncherDragEnd();
AnimateToFinalState();
return;
}
......@@ -487,8 +512,7 @@ void HomeLauncherGestureHandler::OnImplicitAnimationsCompleted() {
}
}
HomeScreenDelegate* home_screen_delegate =
Shell::Get()->home_screen_controller()->delegate();
HomeScreenDelegate* home_screen_delegate = GetHomeScreenDelegate();
DCHECK(home_screen_delegate);
// Return the app list to its original opacity and transform without
......@@ -569,8 +593,7 @@ void HomeLauncherGestureHandler::UpdateSettings(
if (IsDragInProgress())
duration_ms = kAnimationDurationMs;
HomeScreenDelegate* home_screen_delegate =
Shell::Get()->home_screen_controller()->delegate();
HomeScreenDelegate* home_screen_delegate = GetHomeScreenDelegate();
duration_ms = home_screen_delegate->GetOptionalAnimationDuration().value_or(
duration_ms);
......@@ -588,8 +611,7 @@ void HomeLauncherGestureHandler::UpdateWindows(double progress, bool animate) {
const int y_position =
gfx::Tween::IntValueBetween(progress, work_area.bottom(), work_area.y());
const float opacity = gfx::Tween::FloatValueBetween(progress, 0.f, 1.f);
HomeScreenDelegate* home_screen_delegate =
Shell::Get()->home_screen_controller()->delegate();
HomeScreenDelegate* home_screen_delegate = GetHomeScreenDelegate();
DCHECK(home_screen_delegate);
home_screen_delegate->UpdateYPositionAndOpacityForHomeLauncher(
y_position, opacity,
......
......@@ -56,6 +56,12 @@ class HomeScreenDelegate {
// Note: Visibility of the shelf and status area are independent, but the
// variant with shelf visible and status area hidden is currently unsupported.
virtual bool ShouldShowStatusAreaOnHomeScreen() const = 0;
// Triggered when dragging launcher in tablet mode starts/proceeds/ends. They
// cover both dragging launcher to show and hide.
virtual void OnHomeLauncherDragStart() {}
virtual void OnHomeLauncherDragInProgress() {}
virtual void OnHomeLauncherDragEnd() {}
};
} // namespace ash
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment