chromium/chromeos/ash/components/phonehub/connection_scheduler_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 "chromeos/ash/components/phonehub/connection_scheduler_impl.h"

#include "base/functional/bind.h"
#include "base/notreached.h"
#include "base/task/sequenced_task_runner.h"
#include "chromeos/ash/components/multidevice/logging/logging.h"
#include "chromeos/ash/components/phonehub/feature_status.h"
#include "chromeos/ash/components/phonehub/phone_hub_structured_metrics_logger.h"
#include "chromeos/ash/services/secure_channel/public/cpp/client/connection_manager.h"

namespace ash {
namespace phonehub {

constexpr net::BackoffEntry::Policy kRetryBackoffPolicy = {
    0,               // Number of initial errors to ignore.
    10 * 1000,       // Initial delay of 10 seconds in ms.
    2.0,             // Factor by which the waiting time will be multiplied.
    0.2,             // Fuzzing percentage.
    60 * 60 * 1000,  // Maximum delay of 1 hour in ms.
    -1,              // Never discard the entry.
    true,            // Use initial delay.
};

ConnectionSchedulerImpl::ConnectionSchedulerImpl(
    secure_channel::ConnectionManager* connection_manager,
    FeatureStatusProvider* feature_status_provider,
    PhoneHubStructuredMetricsLogger* phone_hub_structured_metrics_logger)
    : connection_manager_(connection_manager),
      feature_status_provider_(feature_status_provider),
      phone_hub_structured_metrics_logger_(phone_hub_structured_metrics_logger),
      retry_backoff_(&kRetryBackoffPolicy) {
  DCHECK(connection_manager_);
  DCHECK(feature_status_provider_);

  current_feature_status_ = feature_status_provider_->GetStatus();
  feature_status_provider_->AddObserver(this);
}

ConnectionSchedulerImpl::~ConnectionSchedulerImpl() {
  feature_status_provider_->RemoveObserver(this);
  weak_ptr_factory_.InvalidateWeakPtrs();
}

void ConnectionSchedulerImpl::ScheduleConnectionNow(
    DiscoveryEntryPoint entry_point) {
  if (feature_status_provider_->GetStatus() !=
      FeatureStatus::kEnabledButDisconnected) {
    PA_LOG(WARNING) << "ScheduleConnectionNow() could not request a connection "
                    << "attempt because the current status is: "
                    << feature_status_provider_->GetStatus() << ".";
    return;
  }

  if (connection_manager_->AttemptNearbyConnection()) {
    phone_hub_structured_metrics_logger_->LogPhoneHubDiscoveryStarted(
        entry_point);
  }
}

void ConnectionSchedulerImpl::OnFeatureStatusChanged() {
  const FeatureStatus previous_feature_status = current_feature_status_;
  current_feature_status_ = feature_status_provider_->GetStatus();

  switch (current_feature_status_) {
    // The following feature states indicate that there is an interruption with
    // establishing connection to the host phone or that the feature is blocked
    // from initiating a connection. Disconnect the existing connection, reset
    // backoffs, and return early.
    case FeatureStatus::kNotEligibleForFeature:
      [[fallthrough]];
    case FeatureStatus::kEligiblePhoneButNotSetUp:
      [[fallthrough]];
    case FeatureStatus::kPhoneSelectedAndPendingSetup:
      [[fallthrough]];
    case FeatureStatus::kDisabled:
      [[fallthrough]];
    case FeatureStatus::kUnavailableBluetoothOff:
      [[fallthrough]];
    case FeatureStatus::kLockOrSuspended:
      DisconnectAndClearBackoffAttempts();
      return;

    // Connection has been established, clear existing backoffs and return
    // early.
    case FeatureStatus::kEnabledAndConnected:
      ClearBackoffAttempts();
      return;

    // Connection is in progress, return and wait for the result.
    case FeatureStatus::kEnabledAndConnecting:
      return;

    // Phone is available for connection, attempt to establish connection.
    case FeatureStatus::kEnabledButDisconnected:
      break;
  }

  DiscoveryEntryPoint entry_point = DiscoveryEntryPoint::kUserSignIn;

  switch (previous_feature_status) {
    case FeatureStatus::kEnabledAndConnecting:
      PA_LOG(WARNING) << "Scheduling connection to retry in: "
                      << retry_backoff_.GetTimeUntilRelease().InSeconds()
                      << " seconds.";

      retry_backoff_.InformOfRequest(/*succeeded=*/false);
      base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
          FROM_HERE,
          base::BindOnce(&ConnectionSchedulerImpl::ScheduleConnectionNow,
                         weak_ptr_factory_.GetWeakPtr(),
                         DiscoveryEntryPoint::kAutomaticConnectionRetry),
          retry_backoff_.GetTimeUntilRelease());
      return;
    case FeatureStatus::kUnavailableBluetoothOff:
      entry_point = DiscoveryEntryPoint::kBluetoothEnabled;
      break;
    case FeatureStatus::kLockOrSuspended:
      entry_point = DiscoveryEntryPoint::kChromebookUnlock;
      break;
    case FeatureStatus::kDisabled:
      entry_point = DiscoveryEntryPoint::kUserEnabledFeature;
      break;
    case FeatureStatus::kEnabledAndConnected:
      entry_point = DiscoveryEntryPoint::kConnectionRetryAfterConnected;
      break;

    // When status transferred from kNotEligibleForFeature directly to
    // kEnabledButDisconnected usually means user just sign in. Break to keep
    // entry_point value to default.
    case FeatureStatus::kNotEligibleForFeature:
      break;
    case FeatureStatus::kEligiblePhoneButNotSetUp:
      [[fallthrough]];
    case FeatureStatus::kPhoneSelectedAndPendingSetup:
      entry_point = DiscoveryEntryPoint::kUserOnboardedToFeature;
      break;

    // Status should not transition from same status.
    case FeatureStatus::kEnabledButDisconnected:
      NOTREACHED_IN_MIGRATION();
      return;
  }

  ScheduleConnectionNow(entry_point);
}

void ConnectionSchedulerImpl::ClearBackoffAttempts() {
  // Remove all pending ScheduleConnectionNow() backoff attempts.
  weak_ptr_factory_.InvalidateWeakPtrs();

  // Reset the state of the backoff so that the next backoff retry starts at
  // the default initial delay.
  retry_backoff_.Reset();
}

void ConnectionSchedulerImpl::DisconnectAndClearBackoffAttempts() {
  ClearBackoffAttempts();

  // Disconnect existing connection or connection attempt.
  connection_manager_->Disconnect();
}

base::TimeDelta
ConnectionSchedulerImpl::GetCurrentBackoffDelayTimeForTesting() {
  return retry_backoff_.GetTimeUntilRelease();
}

int ConnectionSchedulerImpl::GetBackoffFailureCountForTesting() {
  return retry_backoff_.failure_count();
}

}  // namespace phonehub
}  // namespace ash