1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214
  215
  216
  217
  218
  219
  220
  221
  222
  223
  224
  225
  226
  227
  228
  229
  230
  231
  232
  233
  234
  235
  236
  237
  238
  239
  240
  241
  242
  243
  244
  245
  246
  247
  248
  249
  250
  251
  252
  253
  254
  255
  256
  257
  258
  259
  260
  261
  262
  263
  264
  265
  266
  267
  268
  269
  270
  271
  272
  273
  274
  275
  276
  277
  278
  279
  280
  281
  282
  283
  284
  285
  286
  287
  288
  289
  290
  291
  292
  293
  294
  295
  296
  297
  298
  299
  300
  301
  302
  303
  304
  305
  306
  307
  308
  309
  310
  311
  312
  313
  314

ash / wm / splitview / split_view_metrics_controller.h [blame]

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

#ifndef ASH_WM_SPLITVIEW_SPLIT_VIEW_METRICS_CONTROLLER_H_
#define ASH_WM_SPLITVIEW_SPLIT_VIEW_METRICS_CONTROLLER_H_

#include <cstdint>
#include <set>
#include <vector>

#include "ash/constants/ash_pref_names.h"
#include "ash/wm/desks/desks_controller.h"
#include "ash/wm/splitview/split_view_observer.h"
#include "ash/wm/window_state_observer.h"
#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "base/time/time.h"
#include "chromeos/ui/base/window_state_type.h"
#include "ui/aura/env_observer.h"
#include "ui/aura/window_observer.h"
#include "ui/display/display_observer.h"
#include "ui/wm/public/activation_change_observer.h"

namespace aura {
class Window;
}  //  namespace aura

namespace display {
enum class TabletState;
}  // namespace display

namespace ash {
class SplitViewController;

// Public so it can be used by unit tests.
constexpr char kSnapTwoWindowsDurationHistogramName[] =
    "Ash.Snap.SnapTwoWindowsDuration";
constexpr char kMinimizeTwoWindowsDurationHistogramName[] =
    "Ash.Snap.MinimizeTwoWindowsDuration";
constexpr char kCloseTwoWindowsDurationHistogramName[] =
    "Ash.Snap.CloseTwoWindowsDuration";
constexpr base::TimeDelta kSequentialSnapActionMinTime = base::Seconds(1);
constexpr base::TimeDelta kSequentialSnapActionMaxTime = base::Hours(50);

// SplitViewMetricsController:
// Manages split view related metrics. Tablet mode split view and clamshell
// split view with overview next to a snapped window are managed by
// `SplitViewController`. The UMA can be recorded by the corresponding methods
// of `SplitViewObserver`.
//
// There is another clamshell split view mode with two windows snapped on both
// sides which is not managed by the `SplitViewController`. Therefore,
// `SplitViewMetricsController` needs to inspect this type of split view mode
// whose entry and exit points are defined as below:
// Entry: When the top two visible windows on the active desk are snapped on
//        the left and right sides respectively, the clamshell split view
//        starts.
// Pause: After the split view starts, an unsnapped window is activated,
//        covering the two windows snapped on both sides. The clamshell split
//        view is paused (accumulate the engagement time without reporting to
//        the UMA).
// End: When no two windows are snapped on both sides or tablet model split view
//        starts, the clamshell split view ends.
class SplitViewMetricsController : public SplitViewObserver,
                                   public display::DisplayObserver,
                                   public aura::WindowObserver,
                                   public WindowStateObserver,
                                   public wm::ActivationChangeObserver,
                                   public DesksController::Observer,
                                   public aura::EnvObserver {
 public:
  // Enumeration of device mode when entering split view.
  // Note that these values are persisted to histograms so existing values
  // should remain unchanged and new values should be added to the end.
  enum class DeviceUIMode {
    kClamshell,
    kTablet,
    kMaxValue = kTablet,
  };

  // Enumeration of device orientation when entering and using split view.
  // Note that these values are persisted to histograms so existing values
  // should remain unchanged and new values should be added to the end.
  enum class DeviceOrientation {
    // Left and right.
    kLandscape,
    // Top and bottom.
    kPortrait,
    kMaxValue = kPortrait,
  };

  // static
  static SplitViewMetricsController* Get(aura::Window* window);

  // `SplitViewMetricsController` is attached to a `SplitViewController` with
  // the same root window.
  explicit SplitViewMetricsController(
      SplitViewController* split_view_controller);
  SplitViewMetricsController(const SplitViewMetricsController&) = delete;
  SplitViewMetricsController& operator=(const SplitViewMetricsController&) =
      delete;
  ~SplitViewMetricsController() override;

  // SplitViewObserver:
  void OnSplitViewStateChanged(SplitViewController::State previous_state,
                               SplitViewController::State state) override;
  void OnSplitViewWindowResized() override;
  void OnSplitViewWindowSwapped() override;

  // display::DisplayObserver:
  void OnDisplayMetricsChanged(const display::Display& display,
                               uint32_t changed_metrics) override;
  void OnDisplayTabletStateChanged(display::TabletState state) override;

  // aura::WindowObserver:
  void OnWindowParentChanged(aura::Window* window,
                             aura::Window* parent) override;
  void OnResizeLoopEnded(aura::Window* window) override;
  void OnWindowDestroyed(aura::Window* window) override;
  void OnWindowRemovingFromRootWindow(aura::Window* window,
                                      aura::Window* new_root) override;

  // WindowStateObserver:
  void OnPostWindowStateTypeChange(WindowState* window_state,
                                   chromeos::WindowStateType old_type) override;

  // wm::ActivationChangeObserver:
  void OnWindowActivated(ActivationReason reason,
                         aura::Window* gained_active,
                         aura::Window* lost_active) override;

  // DesksController::Observer:
  void OnDeskActivationChanged(const Desk* activated,
                               const Desk* deactivated) override;

  // aura::EnvObserver
  void OnWindowInitialized(aura::Window* window) override;

  bool in_split_view_recording() const { return in_split_view_recording_; }

 private:
  // Calls when start to record split view metrics.
  void StartRecordSplitViewMetrics();
  // Calls when stop recording split view metrics.
  void StopRecordSplitViewMetrics();

  // Checks if the `window` is in the `observed_windows_` list. Returns true, if
  // the window is being observed.
  bool IsObservingWindow(aura::Window* window) const;

  // Adds and removes a window to the `observed_windows_` list. Note that adding
  // (removing) window and window state observers should be performed at the
  // same time with adding (removing) observed windows.
  void AddObservedWindow(aura::Window* window);
  void RemoveObservedWindow(aura::Window* window);

  // Attaches a new window to the end of `observed_windows_` (the window is
  // stacked on top). If the window is already in the list, just stacks it on
  // top.
  void AddOrStackWindowOnTop(aura::Window* window);

  // Adds windows on active desk to `observed_windows_` list.
  void InitObservedWindowsOnActiveDesk();
  // Remove all current observed windows.
  void ClearObservedWindows();

  // If there are top windows snapped on both sides, start to record split
  // view metrics. Otherwise, stop recording split view metrics.
  void MaybeStartOrEndRecordBothSnappedClamshellSplitView();

  // Pauses recording of engagement time when a window hides the windows
  // snapped on both sides. Return true, if the recording is paused. Otherwise,
  // return false.
  bool MaybePauseRecordBothSnappedClamshellSplitView();

  // Records and resets the duration between two windows getting snapped.
  void RecordSnapTwoWindowsDuration(const base::TimeDelta& elapsed_time);

  // Records and resets the duration between two snapped windows getting
  // minimized.
  void RecordMinimizeTwoWindowsDuration(const base::TimeDelta& elapsed_time);

  // Records and resets the duration between two snapped windows getting
  // closed.
  void RecordCloseTwoWindowsDuration(const base::TimeDelta& elapsed_time);

  // Starts recording the time if `window_state` was the first snapped window.
  // Ends recording if either:
  // 1. A second window is snapped;
  // 2. The first window is unsnapped;
  // 3. The first window is destroyed.
  void MaybeStartOrEndRecordSnapTwoWindowsDuration(WindowState* window_state);

  // Starts recording the time if `window_state` changed from snapped to
  // minimized. Ends recording if either:
  // 1. A second window state changes from snapped to minimized;
  // 2. The first window is no longer snapped or minimized;
  // 3. The first window is destroyed.
  void MaybeStartOrEndRecordMinimizeTwoWindowsDuration(
      WindowState* window_state,
      chromeos::WindowStateType old_type);

  // Starts recording the time if `window` was snapped and gets closed, i.e.
  // destroyed. Ends recording if either:
  // 1. A second snapped window is closed;
  // 2. A second snapped window is unsnapped.
  void MaybeStartOrEndRecordCloseTwoWindowsDuration(aura::Window* window);

  // Resets the variables related to time and counter metrics.
  void ResetTimeAndCounter();

  // Called from `OnDisplayTabletStateChanged` when the display tablet state
  // transition is completed.
  void OnTabletModeStarted();
  void OnTabletModeEnded();

  // Checks if we are recording clamshell/tablet mode metrics.
  bool IsRecordingClamshellMetrics() const;
  bool IsRecordingTabletMetrics() const;

  // Reports the engagement metrics for both clamshell and tablet split view.
  void StartRecordClamshellSplitView();
  void StopRecordClamshellSplitView();
  void StartRecordTabletSplitView();
  void StopRecordTabletSplitView();

  // Reports the engagement metrics for both multi-display clamshell and tablet
  // split view. Note that the multi-display mode is managed by the split view
  // metrics controllers of all root windows:
  // - After a root window enters split view, the total number of root windows
  //   in split view becomes two, indicating that multi-display split view
  //   started.
  // - After a root window exits split view, the total number of root windows in
  //   split view becomes one, indicating that multi-display split view ended.
  // - In any time, the total number of root windows in split view larger than
  //   one indicating that it is in the multi-display split view.
  void StartRecordClamshellMultiDisplaySplitView();
  void StopRecordClamshellMultiDisplaySplitView();
  void StartRecordTabletMultiDisplaySplitView();
  void StopRecordTabletMultiDisplaySplitView();

  // Called when the display orientation or mode changes to report device mode
  // and orientation the user uses split screen in. This updates UMA metric
  // `Ash.SplitView.DeviceOrientation.{DeviceUIMode}`.
  void ReportDeviceUIModeAndOrientationHistogram();

  // We need to save an ptr of the observed `SplitViewController`. Because the
  // `RootWindowController` will be deconstructed in advance. Then, we cannot
  // use it to get observed `SplitViewController`.
  const raw_ptr<SplitViewController> split_view_controller_;

  // Indicates whether it is recording split view metrics.
  bool in_split_view_recording_ = false;

  // Used to track the change of device orientation.
  DeviceOrientation orientation_ = DeviceOrientation::kLandscape;

  // Current observed desk.
  raw_ptr<const Desk> current_desk_ = nullptr;

  // Observed windows on the active desk.
  std::vector<raw_ptr<aura::Window, VectorExperimental>> observed_windows_;

  // Windows that recovered by window restore have no parents at the initialize
  // stage, so their window states cannot be observed when are inserted into
  // `observed_windows_` list. This set contains the windows recovered by window
  // restored whose window states have not been observed yet.
  std::set<raw_ptr<aura::Window, SetExperimental>> no_state_observed_windows_;

  // Start time of clamshell and tablet split view. When stop recording, the
  // start time will be set to `base::TimeTicks::Max()`. This is also used as an
  // indicator of whether we are recording clamshell/tablet split view.
  base::TimeTicks clamshell_split_view_start_time_;
  base::TimeTicks tablet_split_view_start_time_;

  // An accumulator of clamshell split view engagement time. When the clamshell
  // split view with two windows snapped on both sides is paused (the definition
  // of "pause" is defined in the comments at the beginning), accumulate the
  // current engagement time period.
  int64_t clamshell_split_view_time_ = 0;

  // Counter of resizing windows in split view.
  int tablet_resize_count_ = 0;
  int clamshell_resize_count_ = 0;

  // Counter of swapping windows in split view.
  int swap_count_ = 0;

  // The first window that gets snapped and the time it's snapped at. Used by
  // `Ash.Snap.SnapTwoWindowsDuration` in
  // tools/metrics/histograms/metadata/ash/histograms.xml.
  raw_ptr<aura::Window> first_snapped_window_ = nullptr;
  base::TimeTicks first_snapped_time_;

  // The first snapped window that gets minimized and the time it's minimized.
  // Used by `Ash.Snap.MinimizeTwoWindowsDuration` in
  // tools/metrics/histograms/metadata/ash/histograms.xml.
  raw_ptr<WindowState> first_minimized_window_state_ = nullptr;
  base::TimeTicks first_minimized_time_;

  // The first snapped window that gets closed and the time it's closed.
  // Used by `Ash.Snap.CloseTwoWindowsDuration` in
  // tools/metrics/histograms/metadata/ash/histograms.xml.
  chromeos::WindowStateType first_closed_state_type_ =
      chromeos::WindowStateType::kDefault;
  base::TimeTicks first_closed_time_;

  display::ScopedDisplayObserver display_observer_{this};
};

}  // namespace ash

#endif  // ASH_WM_SPLITVIEW_SPLIT_VIEW_METRICS_CONTROLLER_H_