chromium/chrome/browser/ash/power/auto_screen_brightness/light_provider_mojo.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/power/auto_screen_brightness/light_provider_mojo.h"

#include <iterator>
#include <utility>

#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "chromeos/components/sensors/ash/sensor_hal_dispatcher.h"

namespace {

// Delay of the reconnection to Sensor Hal Dispatcher.
constexpr base::TimeDelta kDelayReconnect = base::Milliseconds(1000);

constexpr base::TimeDelta kNewDevicesTimeout = base::Milliseconds(10000);

constexpr char kCrosECLightName[] = "cros-ec-light";
constexpr char kAcpiAlsName[] = "acpi-als";

}  // namespace

namespace ash {
namespace power {
namespace auto_screen_brightness {

LightProviderMojo::LightProviderMojo(AlsReader* als_reader)
    : LightProviderInterface(als_reader) {
  RegisterSensorClient();
}

LightProviderMojo::~LightProviderMojo() = default;

void LightProviderMojo::SetUpChannel(
    mojo::PendingRemote<chromeos::sensors::mojom::SensorService>
        pending_remote) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (sensor_service_remote_.is_bound()) {
    LOG(ERROR) << "Ignoring the second Remote<SensorService>";
    return;
  }

  DCHECK(!new_devices_observer_.is_bound());

  sensor_service_remote_.Bind(std::move(pending_remote));
  sensor_service_remote_.set_disconnect_handler(
      base::BindOnce(&LightProviderMojo::OnSensorServiceDisconnect,
                     weak_ptr_factory_.GetWeakPtr()));

  if (light_device_id_.has_value()) {
    SetupLightSamplesObserver();

    auto& light = lights_[light_device_id_.value()];
    DCHECK(!light.ignored);
    DCHECK(light.name.has_value());

    if (light.name.value().compare(kCrosECLightName) == 0 &&
        light.on_lid.value_or(false)) {
      return;
    }
  }

  sensor_service_remote_->RegisterNewDevicesObserver(
      new_devices_observer_.BindNewPipeAndPassRemote());
  new_devices_observer_.set_disconnect_handler(
      base::BindOnce(&LightProviderMojo::OnNewDevicesObserverDisconnect,
                     weak_ptr_factory_.GetWeakPtr()));

  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&LightProviderMojo::OnNewDevicesTimeout,
                     weak_ptr_factory_.GetWeakPtr()),
      kNewDevicesTimeout);

  QueryDevices();
}

void LightProviderMojo::OnNewDeviceAdded(
    int32_t iio_device_id,
    const std::vector<chromeos::sensors::mojom::DeviceType>& types) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!base::Contains(types, chromeos::sensors::mojom::DeviceType::LIGHT)) {
    // Not a light sensor. Ignoring this device.
    return;
  }

  RegisterLightWithId(iio_device_id);
}

LightProviderMojo::LightData::LightData() = default;
LightProviderMojo::LightData::~LightData() = default;

void LightProviderMojo::OnNewDevicesObserverDisconnect() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  LOG(ERROR)
      << "OnNewDevicesObserverDisconnect. Mojo connection to IIO "
         "Service is lost. Resetting the SensorService Mojo channel as well";

  // Assumes IIO Service has crashed and waits for its relaunch.
  ResetSensorService();
}

void LightProviderMojo::OnNewDevicesTimeout() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  new_devices_observer_.reset();

  if (als_init_status_set_)
    return;

  als_init_status_set_ = true;
  LOG(ERROR) << "Target light sensor isn't available after timeout. "
                "Initialization failed.";

  lights_.clear();
  ResetSensorService();
  sensor_hal_client_.reset();

  als_reader_->SetAlsInitStatus(AlsReader::AlsInitStatus::kIncorrectConfig);
}

void LightProviderMojo::RegisterSensorClient() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterClient(
      sensor_hal_client_.BindNewPipeAndPassRemote());

  sensor_hal_client_.set_disconnect_handler(
      base::BindOnce(&LightProviderMojo::OnSensorHalClientFailure,
                     weak_ptr_factory_.GetWeakPtr()));
}

void LightProviderMojo::OnSensorHalClientFailure() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  LOG(ERROR) << "OnSensorHalClientFailure. Mojo connection to "
                "SensorHalDispatcher is lost.";

  ResetSensorService();
  sensor_hal_client_.reset();

  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&LightProviderMojo::RegisterSensorClient,
                     weak_ptr_factory_.GetWeakPtr()),
      kDelayReconnect);
}

void LightProviderMojo::OnSensorServiceDisconnect() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  LOG(ERROR)
      << "OnSensorServiceDisconnect. Mojo connection to IIO Service is lost";

  ResetSensorService();
}

void LightProviderMojo::ResetSensorService() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  observer_.reset();
  for (auto& light : lights_)
    light.second.remote.reset();

  new_devices_observer_.reset();
  sensor_service_remote_.reset();
}

void LightProviderMojo::ResetStates() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  observer_.reset();
  light_device_id_.reset();
  lights_.clear();

  if (sensor_service_remote_.is_bound())
    QueryDevices();
}

void LightProviderMojo::QueryDevices() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(sensor_service_remote_.is_bound());

  sensor_service_remote_->GetDeviceIds(
      chromeos::sensors::mojom::DeviceType::LIGHT,
      base::BindOnce(&LightProviderMojo::GetLightIdsCallback,
                     weak_ptr_factory_.GetWeakPtr()));
}

void LightProviderMojo::GetLightIdsCallback(
    const std::vector<int32_t>& light_ids) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  for (int32_t id : light_ids)
    RegisterLightWithId(id);
}

void LightProviderMojo::RegisterLightWithId(int32_t id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(sensor_service_remote_.is_bound());

  auto& light = lights_[id];

  if (light.ignored || light.name.has_value() || light.on_lid.has_value()) {
    // The attributes have already been retrieved. Would've been used if chosen
    // in |SetUpChannel|.
    return;
  }

  DCHECK(!light.remote.is_bound());

  light.remote = GetSensorDeviceRemote(id);

  light.remote->GetAttributes(
      std::vector<std::string>{chromeos::sensors::mojom::kDeviceName,
                               chromeos::sensors::mojom::kLocation},
      base::BindOnce(&LightProviderMojo::GetNameLocationCallback,
                     weak_ptr_factory_.GetWeakPtr(), id));
}

void LightProviderMojo::GetNameLocationCallback(
    int32_t id,
    const std::vector<std::optional<std::string>>& values) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_NE(light_device_id_.value_or(-1), id);

  if (light_device_id_.has_value()) {
    auto& orig_light = lights_[light_device_id_.value()];
    if (orig_light.name.has_value() &&
        orig_light.name.value().compare(kCrosECLightName) == 0 &&
        orig_light.on_lid.value_or(false)) {
      // Already has the cros-ec-light on the lid. Ignoring other light sensors.
      IgnoreLight(id);
      return;
    }
  }

  if (values.size() < 2) {
    LOG(ERROR) << "Sensor values don't contain name and location attribute";
    IgnoreLight(id);
    return;
  }

  if (values.size() != 2) {
    LOG(WARNING)
        << "Sensor values contain more than name and location attribute. Size: "
        << values.size();
  }

  auto& light = lights_[id];
  DCHECK(light.remote.is_bound());

  light.name = values[0];
  light.on_lid =
      values[1].has_value() &&
      (values[1].value().compare(chromeos::sensors::mojom::kLocationLid) == 0);

  if (light.name.has_value() &&
      light.name.value().compare(kCrosECLightName) == 0) {
    if (light.on_lid.value_or(false)) {
      // If a light sensor was chosen, migrate to this cros-ec-light light
      // sensor.
      DetermineLightSensor(id);
      new_devices_observer_.reset();  // Don't need new light sensors anymore.
      return;
    }

    if (light_device_id_.has_value()) {
      auto& orig_light = lights_[light_device_id_.value()];
      if (orig_light.name.has_value() &&
          orig_light.name.value().compare(kCrosECLightName) == 0) {
        // Already has the cros-ec-light. Ignoring other non-lid cros-ec-light.
        IgnoreLight(id);
        return;
      }
    }

    // Choose the current non-lid cros-ec-light.
    DetermineLightSensor(id);
  }

  if (light_device_id_.has_value()) {
    // Use the current light sensor over the current non cros-ec-light.
    IgnoreLight(id);
    return;
  }

  if (!light.name.has_value() ||
      light.name.value().compare(kAcpiAlsName) != 0) {
    LOG(WARNING) << "Unexpected light name: " << light.name.value_or("");
  }

  // Choose the current acpi-als.
  DetermineLightSensor(id);
}

void LightProviderMojo::IgnoreLight(int32_t id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  auto& light = lights_[id];
  light.ignored = true;
  light.remote.reset();
}

mojo::Remote<chromeos::sensors::mojom::SensorDevice>
LightProviderMojo::GetSensorDeviceRemote(int32_t id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(sensor_service_remote_.is_bound());

  auto& light = lights_[id];
  if (light.remote.is_bound()) {
    // Reuse the previous remote.
    return std::move(light.remote);
  }

  mojo::Remote<chromeos::sensors::mojom::SensorDevice> sensor_device_remote;
  sensor_service_remote_->GetDevice(
      id, sensor_device_remote.BindNewPipeAndPassReceiver());
  sensor_device_remote.set_disconnect_with_reason_handler(
      base::BindOnce(&LightProviderMojo::OnLightRemoteDisconnect,
                     weak_ptr_factory_.GetWeakPtr(), id));

  return sensor_device_remote;
}

void LightProviderMojo::OnLightRemoteDisconnect(
    int32_t id,
    uint32_t custom_reason_code,
    const std::string& description) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  const auto reason =
      static_cast<chromeos::sensors::mojom::SensorDeviceDisconnectReason>(
          custom_reason_code);
  LOG(WARNING) << "OnLightRemoteDisconnect: " << id << ", reason: " << reason
               << ", description: " << description;

  // Assumes IIO Service has crashed and waits for its relaunch.
  switch (reason) {
    case chromeos::sensors::mojom::SensorDeviceDisconnectReason::
        IIOSERVICE_CRASHED:
      ResetSensorService();
      break;

    case chromeos::sensors::mojom::SensorDeviceDisconnectReason::DEVICE_REMOVED:
      if (light_device_id_ != id) {
        // This light sensor is not in use.
        lights_.erase(id);
      } else {
        // Reset usages & states, and restart the mojo devices initialization.
        ResetStates();
      }
      break;
  }
}

void LightProviderMojo::DetermineLightSensor(int32_t id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!als_init_status_set_) {
    als_init_status_set_ = true;
    als_reader_->SetAlsInitStatus(AlsReader::AlsInitStatus::kSuccess);
  }

  light_device_id_ = id;
  SetupLightSamplesObserver();
}

void LightProviderMojo::SetupLightSamplesObserver() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(light_device_id_.has_value());

  observer_ = std::make_unique<LightSamplesObserver>(
      als_reader_, GetSensorDeviceRemote(light_device_id_.value()));
}

}  // namespace auto_screen_brightness
}  // namespace power
}  // namespace ash