blob: 4fca813b06971862e348e1124024d0c835cb95f1 [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.
#include "chrome/browser/ash/child_accounts/time_limits/web_time_activity_provider.h"
#include "base/containers/contains.h"
#include "base/time/time.h"
#include "chrome/browser/ash/child_accounts/time_limits/app_activity_registry.h"
#include "chrome/browser/ash/child_accounts/time_limits/app_service_wrapper.h"
#include "chrome/browser/ash/child_accounts/time_limits/app_time_controller.h"
#include "chrome/browser/ash/child_accounts/time_limits/app_time_limit_utils.h"
#include "chrome/browser/ash/child_accounts/time_limits/app_types.h"
#include "chrome/browser/ash/child_accounts/time_limits/web_time_limit_enforcer.h"
#include "chrome/browser/ash/child_accounts/time_limits/web_time_navigation_observer.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "content/public/browser/web_contents.h"
#include "ui/aura/window.h"
namespace ash {
namespace app_time {
namespace {
const Browser* GetBrowserForWindow(const aura::Window* window) {
BrowserList* list = BrowserList::GetInstance();
for (const Browser* browser : *list) {
if (browser->window()->GetNativeWindow() == window)
return browser;
}
return nullptr;
}
const Browser* GetBrowserForTabStripModel(const TabStripModel* model) {
BrowserList* list = BrowserList::GetInstance();
for (const Browser* browser : *list) {
if (browser->tab_strip_model() == model)
return browser;
}
NOTREACHED();
return nullptr;
}
const Browser* GetBrowserForWebContents(const content::WebContents* contents) {
BrowserList* list = BrowserList::GetInstance();
for (const Browser* browser : *list) {
const auto* tab_strip_model = browser->tab_strip_model();
for (int i = 0; i < tab_strip_model->count(); i++) {
if (tab_strip_model->GetWebContentsAt(i) == contents)
return browser;
}
}
NOTREACHED();
return nullptr;
}
} // namespace
WebTimeActivityProvider::WebTimeActivityProvider(
AppTimeController* app_time_controller,
AppServiceWrapper* app_service_wrapper)
: app_time_controller_(app_time_controller),
app_service_wrapper_(app_service_wrapper) {
DCHECK(app_time_controller_);
DCHECK(app_service_wrapper_);
BrowserList::GetInstance()->AddObserver(this);
app_service_wrapper_->AddObserver(this);
}
WebTimeActivityProvider::~WebTimeActivityProvider() {
BrowserList::GetInstance()->RemoveObserver(this);
TabStripModelObserver::StopObservingAll(this);
app_service_wrapper_->RemoveObserver(this);
for (auto* navigation_observer : navigation_observers_) {
navigation_observer->RemoveObserver(this);
}
}
void WebTimeActivityProvider::OnWebActivityChanged(
const WebTimeNavigationObserver::NavigationInfo& info) {
if (info.is_web_app)
return;
const Browser* browser = GetBrowserForWebContents(info.web_contents);
// The browser window is not active. This may happen when a navigation
// finishes in the background.
if (!base::Contains(active_browsers_, browser))
return;
// Navigation finished in a background tab. Return.
if (browser->tab_strip_model()->GetActiveWebContents() != info.web_contents)
return;
MaybeNotifyStateChange(base::Time::Now());
}
void WebTimeActivityProvider::WebTimeNavigationObserverDestroyed(
WebTimeNavigationObserver* navigation_observer) {
navigation_observer->RemoveObserver(this);
navigation_observers_.erase(navigation_observer);
}
void WebTimeActivityProvider::OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) {
if (change.type() == TabStripModelChange::Type::kInserted)
TabsInserted(change.GetInsert());
const Browser* browser = GetBrowserForTabStripModel(tab_strip_model);
// If the Browser is not the active browser, simply return.
if (!base::Contains(active_browsers_, browser))
return;
// Let's check if the active tab changed, or the content::WebContents in the
// active tab was replaced:
bool active_tab_changed = selection.active_tab_changed();
bool web_content_replaced =
change.type() == TabStripModelChange::Type::kReplaced;
if (!(active_tab_changed || web_content_replaced))
return;
MaybeNotifyStateChange(base::Time::Now());
}
void WebTimeActivityProvider::OnBrowserAdded(Browser* browser) {
browser->tab_strip_model()->AddObserver(this);
}
void WebTimeActivityProvider::OnBrowserRemoved(Browser* browser) {
if (!base::Contains(active_browsers_, browser))
return;
active_browsers_.erase(browser);
MaybeNotifyStateChange(base::Time::Now());
}
void WebTimeActivityProvider::OnAppActive(const AppId& app_id,
aura::Window* window,
base::Time timestamp) {
if (app_id != GetChromeAppId())
return;
const Browser* browser = GetBrowserForWindow(window);
if (!browser)
return;
active_browsers_.insert(browser);
MaybeNotifyStateChange(timestamp);
}
void WebTimeActivityProvider::OnAppInactive(const AppId& app_id,
aura::Window* window,
base::Time timestamp) {
if (app_id != GetChromeAppId())
return;
const Browser* browser = GetBrowserForWindow(window);
if (!browser)
return;
if (!base::Contains(active_browsers_, browser))
return;
active_browsers_.erase(browser);
MaybeNotifyStateChange(timestamp);
}
void WebTimeActivityProvider::TabsInserted(
const TabStripModelChange::Insert* insert) {
for (const TabStripModelChange::ContentsWithIndex& content_with_index :
insert->contents) {
WebTimeNavigationObserver* navigation_observer =
WebTimeNavigationObserver::FromWebContents(content_with_index.contents);
// Continue if the navigation observer is not created or if |this| already
// observes it.
if (!navigation_observer ||
base::Contains(navigation_observers_, navigation_observer)) {
continue;
}
navigation_observer->AddObserver(this);
navigation_observers_.insert(navigation_observer);
}
}
void WebTimeActivityProvider::MaybeNotifyStateChange(base::Time timestamp) {
ChromeAppActivityState new_state = CalculateChromeAppActivityState();
if (new_state == chrome_app_activity_state_)
return;
chrome_app_activity_state_ = new_state;
app_time_controller_->app_registry()->OnChromeAppActivityChanged(new_state,
timestamp);
}
ChromeAppActivityState
WebTimeActivityProvider::CalculateChromeAppActivityState() const {
int active_count = 0;
int active_allowlisted_count = 0;
for (const Browser* browser : active_browsers_) {
const content::WebContents* contents =
browser->tab_strip_model()->GetActiveWebContents();
// If the active web content is null, return.
if (!contents)
continue;
const WebTimeNavigationObserver* observer =
WebTimeNavigationObserver::FromWebContents(contents);
// If |observer| is not instantiated, that means that
// WebTimeNavigationObserver::MaybeCreateForWebContents didn't create it.
// This means that WebTimeLimitEnforcer::IsEnabled returned false.
// Mark it as active allowlisted.
if (!observer) {
active_allowlisted_count++;
continue;
}
const absl::optional<WebTimeNavigationObserver::NavigationInfo>& info =
observer->last_navigation_info();
// The first navigation has not occurred yet.
if (!info.has_value())
continue;
if (info->is_web_app)
continue;
WebTimeLimitEnforcer* enforcer = app_time_controller_->web_time_enforcer();
if (info->is_error || enforcer->IsURLAllowlisted(info->url)) {
active_allowlisted_count++;
continue;
}
active_count++;
}
if (active_count > 0)
return ChromeAppActivityState::kActive;
if (active_allowlisted_count > 0)
return ChromeAppActivityState::kActiveAllowlisted;
return ChromeAppActivityState::kInactive;
}
} // namespace app_time
} // namespace ash