Commit 0e1cda6a authored by David Jean's avatar David Jean Committed by Commit Bot

[ios] Window geometry metrics recorder

Add enum histogram for ios multi-window configuration
recording. Each minute one bucket from the enum will
be increased by 1.

Bug: 1134568
Change-Id: I2f5a031c7430d4b8344adbb06806a3c75803263f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2445415Reviewed-by: default avatarIlya Sherman <isherman@chromium.org>
Reviewed-by: default avatarMark Cogan <marq@chromium.org>
Commit-Queue: David Jean <djean@chromium.org>
Cr-Commit-Position: refs/heads/master@{#818402}
parent b65347a0
...@@ -214,6 +214,7 @@ source_set("app_internal") { ...@@ -214,6 +214,7 @@ source_set("app_internal") {
"//ios/chrome/browser/history", "//ios/chrome/browser/history",
"//ios/chrome/browser/main", "//ios/chrome/browser/main",
"//ios/chrome/browser/memory", "//ios/chrome/browser/memory",
"//ios/chrome/browser/metrics:metrics",
"//ios/chrome/browser/metrics:metrics_internal", "//ios/chrome/browser/metrics:metrics_internal",
"//ios/chrome/browser/net", "//ios/chrome/browser/net",
"//ios/chrome/browser/ntp:features", "//ios/chrome/browser/ntp:features",
......
...@@ -69,6 +69,7 @@ ...@@ -69,6 +69,7 @@
#import "ios/chrome/browser/main/browser_list_factory.h" #import "ios/chrome/browser/main/browser_list_factory.h"
#import "ios/chrome/browser/memory/memory_debugger_manager.h" #import "ios/chrome/browser/memory/memory_debugger_manager.h"
#include "ios/chrome/browser/metrics/first_user_action_recorder.h" #include "ios/chrome/browser/metrics/first_user_action_recorder.h"
#import "ios/chrome/browser/metrics/window_configuration_recorder.h"
#import "ios/chrome/browser/net/cookie_util.h" #import "ios/chrome/browser/net/cookie_util.h"
#import "ios/chrome/browser/omaha/omaha_service.h" #import "ios/chrome/browser/omaha/omaha_service.h"
#include "ios/chrome/browser/pref_names.h" #include "ios/chrome/browser/pref_names.h"
...@@ -257,6 +258,8 @@ void MainControllerAuthenticationServiceDelegate::ClearBrowsingData( ...@@ -257,6 +258,8 @@ void MainControllerAuthenticationServiceDelegate::ClearBrowsingData(
// Variable backing metricsMediator property. // Variable backing metricsMediator property.
__weak MetricsMediator* _metricsMediator; __weak MetricsMediator* _metricsMediator;
WindowConfigurationRecorder* _windowConfigurationRecorder;
// Hander for the startup tasks, deferred or not. // Hander for the startup tasks, deferred or not.
StartupTasks* _startupTasks; StartupTasks* _startupTasks;
} }
...@@ -527,6 +530,8 @@ void MainControllerAuthenticationServiceDelegate::ClearBrowsingData( ...@@ -527,6 +530,8 @@ void MainControllerAuthenticationServiceDelegate::ClearBrowsingData(
self.appState.mainBrowserState); self.appState.mainBrowserState);
} }
_windowConfigurationRecorder = [[WindowConfigurationRecorder alloc] init];
return needRestoration; return needRestoration;
} }
......
...@@ -41,6 +41,8 @@ source_set("metrics") { ...@@ -41,6 +41,8 @@ source_set("metrics") {
"mobile_session_shutdown_metrics_provider.mm", "mobile_session_shutdown_metrics_provider.mm",
"pageload_foreground_duration_tab_helper.h", "pageload_foreground_duration_tab_helper.h",
"pageload_foreground_duration_tab_helper.mm", "pageload_foreground_duration_tab_helper.mm",
"window_configuration_recorder.h",
"window_configuration_recorder.mm",
] ]
public_deps = [ "//components/ukm/ios:ukm_url_recorder" ] public_deps = [ "//components/ukm/ios:ukm_url_recorder" ]
deps = [ deps = [
...@@ -79,6 +81,7 @@ source_set("metrics") { ...@@ -79,6 +81,7 @@ source_set("metrics") {
"//ios/chrome/browser/tabs", "//ios/chrome/browser/tabs",
"//ios/chrome/browser/translate", "//ios/chrome/browser/translate",
"//ios/chrome/browser/ui/overscroll_actions", "//ios/chrome/browser/ui/overscroll_actions",
"//ios/chrome/browser/ui/util:multiwindow_util",
"//ios/chrome/browser/ui/whats_new:utils", "//ios/chrome/browser/ui/whats_new:utils",
"//ios/chrome/browser/variations", "//ios/chrome/browser/variations",
"//ios/chrome/browser/variations:ios_chrome_ui_string_overrider_factory", "//ios/chrome/browser/variations:ios_chrome_ui_string_overrider_factory",
......
// Copyright 2020 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.
#ifndef IOS_CHROME_BROWSER_METRICS_WINDOW_CONFIGURATION_RECORDER_H_
#define IOS_CHROME_BROWSER_METRICS_WINDOW_CONFIGURATION_RECORDER_H_
#import <UIKit/UIKit.h>
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class WindowConfiguration {
kUnspecified = 0,
// Chrome is showing one window, fullscreen.
kFullscreen,
// Chrome is showing two windows, one fullscreen and one slide-over.
kFullscreenWithSlideover,
// Chrome is showing one standard-width window, and another app has
// a window alongside it.
kSharedStandard,
// As above, and Chrome also has a slide-over window.
kSharedStandardWithSlideover,
// Chrome is showing one compact-width window, and another app has
// a window alongside it.
kSharedCompact,
// As above, and Chrome also has a slide-over window.
kSharedCompactWithSlideover,
// Chrome has only one foreground window, and it's in slide-over mode.
kSlideoverOnly,
// Chrome has two windows, both standard-width
kStandardBesideStandard,
// As above, and Chrome also has a third window in slide-over mode.
kStandardBesideStandardWithSlideover,
// Chrome has two windows, one standard-width and one compact-width.
kStandardBesideCompact,
// As above, and Chrome also has a third window in slide-over mode.
kStandardBesideCompactWithSlideover,
// Chrome has two windows, both compact-width
kCompactBesideCompact,
// As above, and Chrome also has a third window in slide-over mode.
kCompactBesideCompactWithSlideover,
// NOTE: add new configurations above this line.
kMaxValue = kCompactBesideCompactWithSlideover
};
// Reports time spent for each MultiWindow configuration.
// It looks at the window configuration every minute, and increments the
// histogram bucket for that configuration. Suspends when app is
// backgrounded and restarts when foregrounded.
@interface WindowConfigurationRecorder : NSObject
// State of recording.
@property(nonatomic) BOOL recording;
@end
@interface WindowConfigurationRecorder (VisibleForTesting)
// Computes configuration from given screen and windows.
- (WindowConfiguration)configurationForScreen:(UIScreen*)screen
windows:(NSArray<UIWindow*>*)windows;
@end
#endif // IOS_CHROME_BROWSER_METRICS_WINDOW_CONFIGURATION_RECORDER_H_
// Copyright 2020 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.
#import "ios/chrome/browser/metrics/window_configuration_recorder.h"
#include "base/check.h"
#include "base/mac/foundation_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/timer/timer.h"
#import "ios/chrome/browser/ui/util/multi_window_support.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface WindowConfigurationRecorder ()
// Called by the recording_timer_ to record current config.
- (void)recordConfiguration;
@end
namespace {
// Delay between a recording of a new configuration.
static constexpr base::TimeDelta kRecordDelay =
base::TimeDelta::FromSeconds(20);
// Timer callback for recording configuration after a delay.
void RecordWindowGeometryMetrics(WindowConfigurationRecorder* recorder) {
[recorder recordConfiguration];
}
// Returns all Foreground active windows that are Chrome windows.
NSArray<UIWindow*>* ForegroundWindowsForApplication(
UIApplication* application) {
NSMutableArray<UIWindow*>* windows = [NSMutableArray arrayWithCapacity:3];
if (IsSceneStartupSupported()) {
if (@available(iOS 13, *)) {
for (UIScene* scene in application.connectedScenes) {
if (scene.activationState != UISceneActivationStateForegroundActive)
continue;
UIWindowScene* windowScene = base::mac::ObjCCast<UIWindowScene>(scene);
for (UIWindow* window in windowScene.windows) {
// Skip other windows (like keyboard) that keep showing up.
if (![window isKindOfClass:NSClassFromString(@"ChromeOverlayWindow")])
continue;
[windows addObject:window];
break; // Stop after one window per scene. This may be wrong.
}
}
}
}
return [windows copy];
}
} // namespace
@implementation WindowConfigurationRecorder {
// Repeating delay timer.
base::RepeatingTimer recording_timer_;
}
- (instancetype)init {
if (self == [super init]) {
// When the app becomes active, set recording on.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(applicationDidBecomeActive)
name:UIApplicationDidBecomeActiveNotification
object:nil];
// When the app resigns active, turn recording off.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(applicationDidEnterBackground)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[self scheduleRecordConfiguration];
}
return self;
}
// Called when notified of UIApplicationDidBecomeActiveNotification.
- (void)applicationDidBecomeActive {
self.recording = YES;
[self scheduleRecordConfiguration];
}
// Called when notified of UIApplicationDidEnterBackgroundNotification
- (void)applicationDidEnterBackground {
self.recording = NO;
recording_timer_.Stop();
}
// Called to set or reset the timer.
- (void)scheduleRecordConfiguration {
if (recording_timer_.IsRunning()) {
recording_timer_.Reset();
} else {
recording_timer_.Start(
FROM_HERE, kRecordDelay,
base::BindRepeating(&RecordWindowGeometryMetrics, self));
}
}
// Called when the timer actually fires.
- (void)recordConfiguration {
[self recordGeometryForScreen:[UIScreen mainScreen]
windows:ForegroundWindowsForApplication(
UIApplication.sharedApplication)];
}
// Computes configuration for given screen and windows and records it.
- (void)recordGeometryForScreen:(UIScreen*)screen
windows:(NSArray<UIWindow*>*)windows {
WindowConfiguration configuration = [self configurationForScreen:screen
windows:windows];
base::UmaHistogramEnumeration("IOS.MultiWindow.Configuration", configuration);
}
#pragma mark - Visible For Testing
- (WindowConfiguration)configurationForScreen:(UIScreen*)screen
windows:(NSArray<UIWindow*>*)windows {
NSMutableArray<UIWindow*>* fullscreenWindows = [[NSMutableArray alloc] init];
NSMutableArray<UIWindow*>* slideoverWindows = [[NSMutableArray alloc] init];
NSMutableArray<UIWindow*>* sharedWindows = [[NSMutableArray alloc] init];
CGRect screenRect = screen.bounds;
for (UIWindow* window in windows) {
CGRect windowRect = window.frame;
// Is the window full screen?
if (CGRectEqualToRect(screenRect, windowRect)) {
[fullscreenWindows addObject:window];
continue;
}
// Is the window in slideover? Slideover windows are always both shorter
// and narrower than the screen.
if (screenRect.size.width > windowRect.size.width &&
screenRect.size.height > windowRect.size.height) {
[slideoverWindows addObject:window];
continue;
}
// Otherwise, the window is shared. This shouldn't happen if there's
// a fullscreen window.
[sharedWindows addObject:window];
}
WindowConfiguration configuration = WindowConfiguration::kUnspecified;
if (sharedWindows.count == 0) {
if (fullscreenWindows.count > 0) {
configuration = slideoverWindows.count > 0
? WindowConfiguration::kFullscreenWithSlideover
: WindowConfiguration::kFullscreen;
} else if (slideoverWindows.count > 0) {
configuration = WindowConfiguration::kSlideoverOnly;
} else {
// Configuration remains unspecificed -- were there no windows?
}
} else if (sharedWindows.count == 1) {
// Single Shared window cases.
UIUserInterfaceSizeClass sharedWindowSize =
sharedWindows[0].traitCollection.horizontalSizeClass;
if (sharedWindowSize == UIUserInterfaceSizeClassRegular) {
configuration = slideoverWindows.count > 0
? WindowConfiguration::kSharedStandardWithSlideover
: WindowConfiguration::kSharedStandard;
} else if (sharedWindowSize == UIUserInterfaceSizeClassCompact) {
configuration = slideoverWindows.count > 0
? WindowConfiguration::kSharedCompactWithSlideover
: WindowConfiguration::kSharedCompact;
} else {
// Configuration remains unspecified -- shared window has an unspecified
// size class.
}
} else if (sharedWindows.count == 2) {
UIUserInterfaceSizeClass firstWindowSize =
sharedWindows[0].traitCollection.horizontalSizeClass;
UIUserInterfaceSizeClass secondWindowSize =
sharedWindows[1].traitCollection.horizontalSizeClass;
if (firstWindowSize == UIUserInterfaceSizeClassRegular &&
secondWindowSize == UIUserInterfaceSizeClassRegular) {
configuration =
slideoverWindows.count > 0
? WindowConfiguration::kStandardBesideStandardWithSlideover
: WindowConfiguration::kStandardBesideStandard;
} else if (firstWindowSize == UIUserInterfaceSizeClassCompact &&
secondWindowSize == UIUserInterfaceSizeClassCompact) {
configuration =
slideoverWindows.count > 0
? WindowConfiguration::kCompactBesideCompactWithSlideover
: WindowConfiguration::kCompactBesideCompact;
} else if (firstWindowSize != UIUserInterfaceSizeClassUnspecified &&
secondWindowSize != UIUserInterfaceSizeClassUnspecified) {
// Since the sizes are neither both standard, nor both compact, nor is
// either of them unspecified, one must be standard and the other compact.
configuration =
slideoverWindows.count > 0
? WindowConfiguration::kStandardBesideCompactWithSlideover
: WindowConfiguration::kStandardBesideCompact;
} else {
// Configuration remains unspecified -- one of the two shared windows has
// an unspecified size class.
}
} else {
// Configuration remains unspecified -- more than two shared windows.
}
return configuration;
}
@end
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