chromium/chrome/browser/apps/app_service/metrics/website_metrics.h

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

#ifndef CHROME_BROWSER_APPS_APP_SERVICE_METRICS_WEBSITE_METRICS_H_
#define CHROME_BROWSER_APPS_APP_SERVICE_METRICS_WEBSITE_METRICS_H_

#include <map>
#include <optional>

#include "base/containers/flat_map.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/observer_list_types.h"
#include "base/scoped_multi_source_observation.h"
#include "base/scoped_observation.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_list_observer.h"
#include "chrome/browser/ui/browser_tab_strip_tracker.h"
#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/browser/history_service_observer.h"
#include "components/webapps/browser/banners/app_banner_manager.h"
#include "content/public/browser/page.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h"
#include "ui/wm/public/activation_change_observer.h"
#include "ui/wm/public/activation_client.h"
#include "url/gurl.h"

class Browser;
class Profile;

namespace webapps {
enum class InstallableWebAppCheckResult;
struct WebAppBannerData;
}  // namespace webapps

namespace apps {

class WebsiteMetricsBrowserTest;
class TestWebsiteMetrics;

extern const char kWebsiteUsageTime[];
extern const char kRunningTimeKey[];
extern const char kUrlContentKey[];
extern const char kPromotableKey[];

// WebsiteMetrics monitors creation/deletion of Browser and its
// TabStripModel to record the website usage time metrics.
class WebsiteMetrics : public BrowserListObserver,
                       public TabStripModelObserver,
                       public aura::WindowObserver,
                       public wm::ActivationChangeObserver,
                       public history::HistoryServiceObserver {
 public:
  // Observer that is notified on certain website events like URL opened, URL
  // closed, etc. Observers are expected to register themselves on session
  // initialization so they do not miss out on events that happen before they
  // are registered.
  class Observer : public base::CheckedObserver {
   public:
    Observer() = default;
    Observer(const Observer&) = delete;
    Observer& operator=(const Observer&) = delete;
    ~Observer() override = default;

    // Invoked when a new URL is opened with specified `WebContents`. We also
    // return the URL that was opened in case there are further updates to
    // `WebContents` forcing a new URL opened event that will follow as a
    // separate notification.
    virtual void OnUrlOpened(const GURL& url_opened,
                             ::content::WebContents* web_contents) {}

    // Invoked when a URL is closed with specified `WebContents`. `WebContents`
    // could reflect current URL in case of content navigation, so we also
    // return the URL that was closed.
    virtual void OnUrlClosed(const GURL& url_closed,
                             ::content::WebContents* web_contents) {}

    // Invoked when URL usage metrics are being recorded (per URL that was used,
    // on a 5 minute interval). `running_time` represents the foreground usage
    // time in the last 5 minute interval. We do not track usage per
    // `WebContents` today. There is a possibility of losing out on initial
    // usage metric records if there are delays in observer registration.
    virtual void OnUrlUsage(const GURL& url, base::TimeDelta running_time) {}

    // Invoked when the `WebsiteMetrics` component (being observed) is being
    // destroyed.
    virtual void OnWebsiteMetricsDestroyed() {}
  };

  WebsiteMetrics(Profile* profile, int user_type_by_device_type);

  WebsiteMetrics(const WebsiteMetrics&) = delete;
  WebsiteMetrics& operator=(const WebsiteMetrics&) = delete;

  ~WebsiteMetrics() override;

  // BrowserListObserver overrides:
  void OnBrowserAdded(Browser* browser) override;

  // TabStripModelObserver overrides:
  void OnTabStripModelChanged(
      TabStripModel* tab_strip_model,
      const TabStripModelChange& change,
      const TabStripSelectionChange& selection) override;

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

  // aura::WindowObserver:
  void OnWindowDestroying(aura::Window* window) override;

  // history::HistoryServiceObserver:
  void OnHistoryDeletions(history::HistoryService* history_service,
                          const history::DeletionInfo& deletion_info) override;
  void HistoryServiceBeingDeleted(
      history::HistoryService* history_service) override;

  // Save the usage time records to the local user perf.
  void OnFiveMinutes();

  // Records the usage time UKM each 2 hours.
  void OnTwoHours();

  void AddObserver(Observer* observer);
  void RemoveObserver(Observer* observer);

 private:
  friend class WebsiteMetricsBrowserTest;
  friend class TestWebsiteMetrics;

  // This class monitors the activated WebContent for the activated browser
  // window and notifies a navigation to the WebsiteMetrics.
  class ActiveTabWebContentsObserver
      : public content::WebContentsObserver,
        public webapps::AppBannerManager::Observer {
   public:
    ActiveTabWebContentsObserver(content::WebContents* contents,
                                 WebsiteMetrics* owner);

    ActiveTabWebContentsObserver(const ActiveTabWebContentsObserver&) = delete;
    ActiveTabWebContentsObserver& operator=(
        const ActiveTabWebContentsObserver&) = delete;

    ~ActiveTabWebContentsObserver() override;

    void OnPrimaryPageChanged();

    // content::WebContentsObserver
    void PrimaryPageChanged(content::Page& page) override;
    void WebContentsDestroyed() override;

    // webapps::AppBannerManager::Observer:
    void OnInstallableWebAppStatusUpdated(
        webapps::InstallableWebAppCheckResult result,
        const std::optional<webapps::WebAppBannerData>& data) override;

   private:
    raw_ptr<WebsiteMetrics> owner_;
    base::ScopedObservation<webapps::AppBannerManager,
                            webapps::AppBannerManager::Observer>
        app_banner_manager_observer_{this};
  };

  struct UrlInfo {
    UrlInfo() = default;
    explicit UrlInfo(const base::Value& value);
    ukm::SourceId source_id = ukm::kInvalidSourceId;
    base::TimeTicks start_time;
    // Running time in the past 5 minutes without noise.
    base::TimeDelta running_time_in_five_minutes;
    // Sum `running_time_in_five_minutes` with noise in the past 2 hours:
    // time1 * noise1 + time2 * noise2 + time3 * noise3....
    base::TimeDelta running_time_in_two_hours;

    bool is_activated = false;
    bool promotable = false;

    // Converts the struct UsageTime to base::Value::Dict, e.g.:
    // {
    //    "time": "3600",
    //    "url_content": "scope",
    //    "promotable": "false",
    // }
    base::Value::Dict ConvertToDict() const;
  };

  // Observes the root window's activation client for the OnWindowActivated
  // callback.
  void MaybeObserveWindowActivationClient(aura::Window* window);

  // Removes observing the root window's activation client when the last browser
  // window is closed.
  void MaybeRemoveObserveWindowActivationClient(aura::Window* window);

  void OnTabStripModelChangeInsert(aura::Window* window,
                                   TabStripModel* tab_strip_model,
                                   const TabStripModelChange::Insert& insert,
                                   const TabStripSelectionChange& selection);
  void OnTabStripModelChangeRemove(aura::Window* window,
                                   TabStripModel* tab_strip_model,
                                   const TabStripModelChange::Remove& remove,
                                   const TabStripSelectionChange& selection);
  void OnTabStripModelChangeReplace(
      const TabStripModelChange::Replace& replace);
  void OnActiveTabChanged(aura::Window* window,
                          content::WebContents* old_contents,
                          content::WebContents* new_contents);
  void OnTabClosed(content::WebContents* web_contents);

  // Called by |WebsiteMetrics::ActiveTabWebContentsObserver|.
  virtual void OnWebContentsUpdated(content::WebContents* web_contents);
  virtual void OnInstallableWebAppStatusUpdated(
      content::WebContents* web_contents,
      webapps::InstallableWebAppCheckResult result,
      const std::optional<webapps::WebAppBannerData>& data);

  // Adds the url info to `url_infos_`.
  void AddUrlInfo(const GURL& url,
                  ukm::SourceId source_id,
                  const base::TimeTicks& start_time,
                  bool is_activated,
                  bool promotable);

  // Modifies `url_infos_` to set whether the website can be promoted to PWA,
  // when the website manifest is updated.
  void UpdateUrlInfo(const GURL& old_url, bool promotable);

  void SetWindowActivated(aura::Window* window);

  void SetWindowInActivated(aura::Window* window);

  void SetTabActivated(content::WebContents* web_contents);

  void SetTabInActivated(content::WebContents* web_contents);

  // Saves the website usage time in `url_infos_` to the user pref each 5
  // minutes.
  void SaveUsageTime();

  // Records the website usage time metrics each 2 hours.
  void RecordUsageTime();

  // Records the usage time UKM saved in the user pref at the first 5 minutes
  // after the user logs in.
  void RecordUsageTimeFromPref();

  void EmitUkm(ukm::SourceId source_id,
               int64_t usage_time,
               bool promotable,
               bool is_from_last_login);

  const raw_ptr<Profile> profile_;

  BrowserTabStripTracker browser_tab_strip_tracker_;

  // The map from the window to the active tab contents.
  base::flat_map<aura::Window*, content::WebContents*> window_to_web_contents_;

  // The map from the root window's activation client to windows.
  std::map<wm::ActivationClient*,
           std::set<raw_ptr<aura::Window, SetExperimental>>>
      activation_client_to_windows_;

  std::map<content::WebContents*, std::unique_ptr<ActiveTabWebContentsObserver>>
      webcontents_to_observer_map_;

  // The map from the web_contents to the ukm key url. When the url for web
  // contents is updated in OnWebContentsUpdated, we can get the previous url
  // from this map to calculate the usage time for the previous url.
  //
  // If the url is used for an app, it won't be added to the map, because the
  // app metrics can record the usage time metrics.
  //
  // If the website has a manifest, we might use the scope or the start url as
  // the ukm key url. Otherwise, the visible url is used as the ukm key url.
  std::map<content::WebContents*, GURL> webcontents_to_ukm_key_;

  // Saves the usage info for the activated urls in activated windows for the
  // UKM records. `url_infos_` is cleared after recording the UKM each 2 hours.
  std::map<GURL, UrlInfo> url_infos_;

  int user_type_by_device_type_ = 0;

  bool should_record_ukm_from_pref_ = true;

  base::ScopedMultiSourceObservation<aura::Window, aura::WindowObserver>
      observed_windows_{this};

  // For Lacros browser windows, there could be multiple root windows for
  // browser windows, and multiple ActivationClients.
  base::ScopedMultiSourceObservation<wm::ActivationClient,
                                     wm::ActivationChangeObserver>
      activation_client_observations_{this};

  base::ScopedObservation<history::HistoryService,
                          history::HistoryServiceObserver>
      history_observation_{this};

  base::ObserverList<Observer> observers_;

  base::WeakPtrFactory<WebsiteMetrics> weak_factory_{this};
};

}  // namespace apps

#endif  // CHROME_BROWSER_APPS_APP_SERVICE_METRICS_WEBSITE_METRICS_H_