chromium/chrome/browser/ash/phonehub/browser_tabs_model_provider_impl.cc

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

#include "chrome/browser/ash/phonehub/browser_tabs_model_provider_impl.h"

#include "base/memory/raw_ptr.h"
#include "chrome/browser/ash/crosapi/browser_util.h"
#include "chrome/browser/ash/sync/synced_session_client_ash.h"
#include "chromeos/ash/components/multidevice/remote_device_ref.h"
#include "chromeos/ash/components/phonehub/browser_tabs_metadata_fetcher.h"
#include "chromeos/ash/components/phonehub/browser_tabs_model.h"
#include "components/sync/base/data_type.h"
#include "components/sync/base/features.h"
#include "components/sync/service/sync_service.h"
#include "components/sync_sessions/open_tabs_ui_delegate.h"
#include "components/sync_sessions/session_sync_service.h"

namespace ash::phonehub {

// static
bool BrowserTabsModelProviderImpl::IsLacrosSessionSyncFeatureEnabled() {
  return !crosapi::browser_util::IsAshWebBrowserEnabled() &&
         base::FeatureList::IsEnabled(syncer::kChromeOSSyncedSessionSharing);
}

BrowserTabsModelProviderImpl::BrowserTabsModelProviderImpl(
    multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client,
    SyncedSessionClientAsh* synced_session_client_ash,
    syncer::SyncService* sync_service,
    sync_sessions::SessionSyncService* session_sync_service,
    std::unique_ptr<BrowserTabsMetadataFetcher> browser_tabs_metadata_fetcher)
    : multidevice_setup_client_(multidevice_setup_client),
      synced_session_client_ash_(synced_session_client_ash),
      sync_service_(sync_service),
      session_sync_service_(session_sync_service),
      browser_tabs_metadata_fetcher_(std::move(browser_tabs_metadata_fetcher)) {
  multidevice_setup_client_->AddObserver(this);
  if (IsLacrosSessionSyncFeatureEnabled()) {
    if (synced_session_client_ash_) {
      synced_session_client_ash_->AddObserver(this);
      // Fetch Browser Metadata for cached foreign synced phone sessions.
      OnForeignSyncedPhoneSessionsUpdated(
          synced_session_client_ash_->last_foreign_synced_phone_sessions());
    }
  } else {
    session_updated_subscription_ =
        session_sync_service->SubscribeToForeignSessionsChanged(
            base::BindRepeating(
                &BrowserTabsModelProviderImpl::AttemptBrowserTabsModelUpdate,
                base::Unretained(this)));
    AttemptBrowserTabsModelUpdate();
  }
}

BrowserTabsModelProviderImpl::~BrowserTabsModelProviderImpl() {
  multidevice_setup_client_->RemoveObserver(this);
  if (IsLacrosSessionSyncFeatureEnabled()) {
    if (synced_session_client_ash_) {
      synced_session_client_ash_->RemoveObserver(this);
    }
  }
}

std::optional<std::string> BrowserTabsModelProviderImpl::GetHostDeviceName()
    const {
  const multidevice_setup::MultiDeviceSetupClient::HostStatusWithDevice&
      host_device_with_status = multidevice_setup_client_->GetHostStatus();
  if (!host_device_with_status.second) {
    return std::nullopt;
  }
  // The pii_free_name field of the device matches the session name for
  // sync.
  return host_device_with_status.second->pii_free_name();
}

void BrowserTabsModelProviderImpl::OnHostStatusChanged(
    const multidevice_setup::MultiDeviceSetupClient::HostStatusWithDevice&
        host_device_with_status) {
  if (IsLacrosSessionSyncFeatureEnabled()) {
    if (synced_session_client_ash_) {
      OnForeignSyncedPhoneSessionsUpdated(
          synced_session_client_ash_->last_foreign_synced_phone_sessions());
    }
  } else {
    AttemptBrowserTabsModelUpdate();
  }
}

void BrowserTabsModelProviderImpl::TriggerRefresh() {
  // crbug/1158480: Currently (January 2021), updates to synced sessions
  // sometimes take a long time to arrive. As a workaround,
  // SyncService::TriggerRefresh() is used, which bypasses some of the potential
  // sources of latency (e.g. for delivering an invalidation), but not others
  // (e.g. backend replication delay). I.e SyncService::TriggerRefresh() will
  // not guarantee an immediate update.
  sync_service_->TriggerRefresh({syncer::SESSIONS});
}

bool BrowserTabsModelProviderImpl::IsBrowserTabSyncEnabled() {
  DCHECK(IsLacrosSessionSyncFeatureEnabled());
  if (!synced_session_client_ash_) {
    return false;
  }

  return synced_session_client_ash_->is_session_sync_enabled();
}

void BrowserTabsModelProviderImpl::AttemptBrowserTabsModelUpdate() {
  std::optional<std::string> host_device_name = GetHostDeviceName();
  sync_sessions::OpenTabsUIDelegate* open_tabs =
      session_sync_service_->GetOpenTabsUIDelegate();
  // Tab sync is disabled or no valid |pii_free_name_|.
  if (!open_tabs || !host_device_name) {
    InvalidateWeakPtrsAndClearTabMetadata(/*is_tab_sync_enabled=*/false);
    return;
  }

  std::vector<raw_ptr<const sync_sessions::SyncedSession, VectorExperimental>>
      sessions;
  bool was_fetch_successful = open_tabs->GetAllForeignSessions(&sessions);
  // No tabs were found, clear all tab metadata.
  if (!was_fetch_successful) {
    InvalidateWeakPtrsAndClearTabMetadata(/*is_tab_sync_enabled=*/true);
    return;
  }

  // |phone_session| should have the same |session_name| as the
  // |pii_free_name|. If there is more than one session with the same
  // |session_name| as the |pii_free_name|, as would be the case if a user has
  // multiple phones of the same type, |phone_session| will have the latest
  // |modified_time|.
  const sync_sessions::SyncedSession* phone_session = nullptr;
  for (const sync_sessions::SyncedSession* session : sessions) {
    if (session->GetSessionName() != *host_device_name) {
      continue;
    }

    if (!phone_session ||
        phone_session->GetModifiedTime() < session->GetModifiedTime()) {
      phone_session = session;
    }
  }

  // No session with the same name as |pii_free_name_| was found, clear all
  // tab metadata.
  if (!phone_session) {
    InvalidateWeakPtrsAndClearTabMetadata(/*is_tab_sync_enabled=*/true);
    return;
  }

  browser_tabs_metadata_fetcher_->Fetch(
      phone_session,
      base::BindOnce(&BrowserTabsModelProviderImpl::OnMetadataFetched,
                     weak_ptr_factory_.GetWeakPtr()));
}

void BrowserTabsModelProviderImpl::InvalidateWeakPtrsAndClearTabMetadata(
    bool is_tab_sync_enabled) {
  weak_ptr_factory_.InvalidateWeakPtrs();
  NotifyBrowserTabsUpdated(
      /*is_tab_sync_enabled=*/is_tab_sync_enabled, {});
}

void BrowserTabsModelProviderImpl::OnMetadataFetched(
    std::optional<std::vector<BrowserTabsModel::BrowserTabMetadata>> metadata) {
  // The operation to fetch metadata was cancelled.
  if (!metadata) {
    return;
  }
  NotifyBrowserTabsUpdated(
      /*is_tab_sync_enabled=*/true, *metadata);
}

// TODO(b/260599791): Updating this method signature to remove the |sessions|
// parameter and utilize the getter in |synced_session_client_ash_| instead.
void BrowserTabsModelProviderImpl::OnForeignSyncedPhoneSessionsUpdated(
    const std::vector<ForeignSyncedSessionAsh>& phone_sessions) {
  DCHECK(IsLacrosSessionSyncFeatureEnabled());
  DCHECK(synced_session_client_ash_);

  if (!synced_session_client_ash_->is_session_sync_enabled()) {
    InvalidateWeakPtrsAndClearTabMetadata(/*is_tab_sync_enabled=*/false);
    return;
  }

  std::optional<std::string> host_device_name = GetHostDeviceName();

  // Tab sync is disabled or no valid |pii_free_name_|.
  if (!host_device_name) {
    InvalidateWeakPtrsAndClearTabMetadata(/*is_tab_sync_enabled=*/false);
    return;
  }

  // No tabs were found, clear all tab metadata.
  if (phone_sessions.empty()) {
    InvalidateWeakPtrsAndClearTabMetadata(/*is_tab_sync_enabled=*/true);
    return;
  }

  std::optional<ForeignSyncedSessionAsh> host_phone_session;
  for (const ForeignSyncedSessionAsh& session : phone_sessions) {
    if (session.session_name != *host_device_name) {
      continue;
    }
    if (!host_phone_session ||
        host_phone_session->modified_time < session.modified_time) {
      host_phone_session = session;
    }
  }
  // No session with the same name as |pii_free_name_| was found, clear all
  // tab metadata.
  if (!host_phone_session) {
    InvalidateWeakPtrsAndClearTabMetadata(/*is_tab_sync_enabled=*/true);
    return;
  }
  browser_tabs_metadata_fetcher_->FetchForeignSyncedPhoneSessionMetadata(
      host_phone_session.value(), synced_session_client_ash_,
      base::BindOnce(&BrowserTabsModelProviderImpl::OnMetadataFetched,
                     weak_ptr_factory_.GetWeakPtr()));
}

void BrowserTabsModelProviderImpl::OnSessionSyncEnabledChanged(bool enabled) {
  DCHECK(IsLacrosSessionSyncFeatureEnabled());
  DCHECK(synced_session_client_ash_);

  if (enabled) {
    OnForeignSyncedPhoneSessionsUpdated(
        synced_session_client_ash_->last_foreign_synced_phone_sessions());
  } else {
    InvalidateWeakPtrsAndClearTabMetadata(/*is_tab_sync_enabled=*/false);
  }
}

}  // namespace ash::phonehub