blob: ae1cf280f070165c23b0633c4a298994cf3cb4fc [file] [log] [blame]
// 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/incognito_usage_app_state_agent.h"
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#import "ios/chrome/app/application_delegate/app_state.h"
#import "ios/chrome/browser/ui/main/scene_state.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// Minimum amount of time for a normal/incognito transition to be considered.
const int kMinimumDelayInSeconds = 10;
}
@interface IncognitoUsageAppStateAgent () <AppStateObserver, SceneStateObserver>
// Observed app state.
@property(nonatomic, weak) AppState* appState;
@property(nonatomic, assign) BOOL incognitoContentVisible;
@property(nonatomic, assign) base::TimeTicks incognitoUsageStart;
@property(nonatomic, assign) base::TimeTicks incognitoUsageEnd;
@end
@implementation IncognitoUsageAppStateAgent
- (instancetype)init {
self = [super init];
if (self) {
[NSNotificationCenter.defaultCenter
addObserver:self
selector:@selector(applicationWillTerminate)
name:UIApplicationWillTerminateNotification
object:nil];
}
return self;
}
- (BOOL)checkIncognitoContentVisible {
for (SceneState* scene in self.appState.connectedScenes) {
if (scene.incognitoContentVisible &&
scene.activationLevel >= SceneActivationLevelForegroundInactive) {
return YES;
}
}
return NO;
}
- (void)reportIncognitoUsageTime {
DCHECK(!self.incognitoUsageStart.is_null());
DCHECK(!self.incognitoUsageEnd.is_null());
base::TimeDelta duration = self.incognitoUsageEnd - self.incognitoUsageStart;
if (duration.InSecondsF() < kMinimumDelayInSeconds) {
return;
}
base::UmaHistogramCustomTimes(
"IOS.Incognito.TimeSpent", duration, base::TimeDelta::FromSeconds(1),
base::TimeDelta::FromSeconds(86400 /* secs per day */), 50);
self.incognitoUsageStart = base::TimeTicks();
self.incognitoUsageEnd = base::TimeTicks();
}
- (void)updateIncognitoContentVisible {
BOOL incognitoContentVisible = [self checkIncognitoContentVisible];
if (self.incognitoContentVisible == incognitoContentVisible) {
return;
}
self.incognitoContentVisible = incognitoContentVisible;
if (incognitoContentVisible) {
base::TimeTicks now = base::TimeTicks::Now();
if (!self.incognitoUsageEnd.is_null() &&
(now - self.incognitoUsageEnd).InSecondsF() < kMinimumDelayInSeconds) {
// The pausing of incognito is too short, resume session.
self.incognitoUsageEnd = base::TimeTicks();
} else {
// Incognito has been paused for a long time. This is a new session.
if (!self.incognitoUsageEnd.is_null() &&
!self.incognitoUsageStart.is_null() &&
(self.incognitoUsageEnd - self.incognitoUsageStart).InSecondsF() >=
kMinimumDelayInSeconds) {
// There was a previous session to report.
[self reportIncognitoUsageTime];
}
self.incognitoUsageStart = base::TimeTicks::Now();
}
} else {
base::TimeTicks now = base::TimeTicks::Now();
if (!self.incognitoUsageStart.is_null() &&
(now - self.incognitoUsageStart).InSecondsF() >=
kMinimumDelayInSeconds) {
self.incognitoUsageEnd = now;
} else {
// This incognito session was too short.
self.incognitoUsageStart = base::TimeTicks();
}
}
}
- (void)applicationWillTerminate {
if (self.incognitoContentVisible) {
self.incognitoUsageEnd = base::TimeTicks::Now();
}
if (!self.incognitoUsageEnd.is_null() &&
!self.incognitoUsageStart.is_null() &&
(self.incognitoUsageEnd - self.incognitoUsageStart).InSecondsF() >=
kMinimumDelayInSeconds) {
[self reportIncognitoUsageTime];
}
}
#pragma mark - AppStateAgent
- (void)setAppState:(AppState*)appState {
// This should only be called once!
DCHECK(!_appState);
_appState = appState;
[appState addObserver:self];
}
#pragma mark - AppStateObserver
- (void)appState:(AppState*)appState sceneConnected:(SceneState*)sceneState {
[sceneState addObserver:self];
[self updateIncognitoContentVisible];
}
#pragma mark - SceneStateObserver
- (void)sceneState:(SceneState*)sceneState
transitionedToActivationLevel:(SceneActivationLevel)level {
if (sceneState.incognitoContentVisible) {
[self updateIncognitoContentVisible];
}
}
- (void)sceneState:(SceneState*)sceneState
isDisplayingIncognitoContent:(BOOL)level {
if (sceneState.activationLevel >= SceneActivationLevelForegroundInactive) {
[self updateIncognitoContentVisible];
}
}
@end