chromium/ash/webui/personalization_app/resources/js/ambient/ambient_observer.ts

// 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.

import {isNonEmptyArray} from 'chrome://resources/ash/common/sea_pen/sea_pen_utils.js';
import {Url} from 'chrome://resources/mojo/url/mojom/url.mojom-webui.js';

import {AmbientModeAlbum, AmbientObserverInterface, AmbientObserverReceiver, AmbientProviderInterface, AmbientTheme, AmbientUiVisibility, TemperatureUnit, TopicSource} from '../../personalization_app.mojom-webui.js';
import {isAmbientModeAllowed} from '../load_time_booleans.js';
import {logGooglePhotosPreviewsLoadTime} from '../personalization_metrics_logger.js';
import {Paths} from '../personalization_router_element.js';
import {PersonalizationStore} from '../personalization_store.js';
import {isRecentHighlightsAlbum} from '../utils.js';

import {setAlbumsAction, setAmbientModeEnabledAction, setAmbientThemeAction, setAmbientUiVisibilityAction, setGeolocationPermissionEnabledAction, setPreviewsAction, setScreenSaverDurationAction, setTemperatureUnitAction, setTopicSourceAction} from './ambient_actions.js';
import {getAmbientProvider} from './ambient_interface_provider.js';

/** @fileoverview listens for updates on ambient mode changes. */

let instance: AmbientObserver|null = null;

/**
 * Observes ambient mode changes and saves updates to PersonalizationStore.
 */
export class AmbientObserver implements AmbientObserverInterface {
  // Allow logging first load performance if the user began on a page where
  // preview images are loaded immediately.
  static shouldLogPreviewsLoadPerformance: boolean =
      window.location.pathname === Paths.ROOT ||
      window.location.pathname === Paths.AMBIENT;

  static initAmbientObserverIfNeeded(): void {
    if (isAmbientModeAllowed() && !instance) {
      instance = new AmbientObserver();
    }
  }

  static shutdown() {
    if (instance) {
      instance.receiver_.$.close();
      instance = null;
    }
  }

  private receiver_: AmbientObserverReceiver;

  constructor() {
    const provider = getAmbientProvider();
    this.receiver_ = this.initReceiver_(provider);
    provider.fetchSettingsAndAlbums();
  }

  private initReceiver_(ambientProvider: AmbientProviderInterface):
      AmbientObserverReceiver {
    const receiver = new AmbientObserverReceiver(this);
    ambientProvider.setAmbientObserver(receiver.$.bindNewPipeAndPassRemote());
    return receiver;
  }

  onAmbientModeEnabledChanged(ambientModeEnabled: boolean) {
    // Only record google photos previews load performance if ambient mode
    // starts enabled.
    AmbientObserver.shouldLogPreviewsLoadPerformance =
        AmbientObserver.shouldLogPreviewsLoadPerformance && ambientModeEnabled;
    const store = PersonalizationStore.getInstance();
    store.dispatch(setAmbientModeEnabledAction(ambientModeEnabled));
  }

  onAmbientThemeChanged(ambientTheme: AmbientTheme): void {
    const store = PersonalizationStore.getInstance();
    store.dispatch(setAmbientThemeAction(ambientTheme));
  }

  onScreenSaverDurationChanged(minutes: number): void {
    const store = PersonalizationStore.getInstance();
    store.dispatch(setScreenSaverDurationAction(minutes));
  }

  onTopicSourceChanged(topicSource: TopicSource) {
    const store = PersonalizationStore.getInstance();
    // If the first time receiving `topicSource`, allow logging load
    // performance.
    AmbientObserver.shouldLogPreviewsLoadPerformance =
        AmbientObserver.shouldLogPreviewsLoadPerformance &&
        store.data.ambient.topicSource === null;
    store.dispatch(setTopicSourceAction(topicSource));
  }

  onTemperatureUnitChanged(temperatureUnit: TemperatureUnit) {
    const store = PersonalizationStore.getInstance();
    store.dispatch(setTemperatureUnitAction(temperatureUnit));
  }

  onAlbumsChanged(albums: AmbientModeAlbum[]) {
    const store = PersonalizationStore.getInstance();

    // Prevent recent highlights album from constantly changing preview image
    // when albums are refreshed during a single session.
    const oldRecentHighlightsAlbum =
        (store.data.ambient.albums ||
         []).find(album => isRecentHighlightsAlbum(album));
    if (oldRecentHighlightsAlbum) {
      const newRecentHighlightsAlbum =
          albums.find(album => isRecentHighlightsAlbum(album));
      if (newRecentHighlightsAlbum) {
        // Edit by reference.
        newRecentHighlightsAlbum.url = oldRecentHighlightsAlbum.url;
      }
    }

    store.dispatch(setAlbumsAction(albums));
  }

  onPreviewsFetched(previews: Url[]) {
    const store = PersonalizationStore.getInstance();

    // Only log performance metrics if this is the first time receiving
    // thumbnails.
    AmbientObserver.shouldLogPreviewsLoadPerformance =
        AmbientObserver.shouldLogPreviewsLoadPerformance &&
        (!store.data.ambient.previews ||
         store.data.ambient.previews.length === 0);

    store.dispatch(setPreviewsAction(previews));

    if (AmbientObserver.shouldLogPreviewsLoadPerformance &&
        isNonEmptyArray(previews)) {
      logGooglePhotosPreviewsLoadTime();
      AmbientObserver.shouldLogPreviewsLoadPerformance = false;
    }
  }

  onAmbientUiVisibilityChanged(ambientUiVisibility: AmbientUiVisibility) {
    const store = PersonalizationStore.getInstance();
    store.dispatch(setAmbientUiVisibilityAction(ambientUiVisibility));
  }

  onGeolocationPermissionForSystemServicesChanged(enabled: boolean): void {
    const store = PersonalizationStore.getInstance();
    store.dispatch(setGeolocationPermissionEnabledAction(enabled));
  }
}