chromium/chrome/browser/ui/webui/ash/sensor_info/sensor_page_handler.cc

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

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/browser/ui/webui/ash/sensor_info/sensor_page_handler.h"

#include <string>
#include <utility>

#include "ash/sensor_info/sensor_provider.h"
#include "ash/sensor_info/sensor_types.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "chrome/browser/ash/file_manager/filesystem_api_util.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "mojo/public/cpp/bindings/pending_remote.h"

namespace ash {

SensorPageHandler::SensorPageHandler(
    Profile* profile,
    SensorProvider* provider,
    mojo::PendingReceiver<sensor::mojom::PageHandler> receiver,
    base::FilePath out_file_path)
    : profile_(profile),
      provider_(provider),
      receiver_(this, std::move(receiver)),
      out_file_path_(std::move(out_file_path)) {
  provider_->AddObserver(this);
  provider_->EnableSensorReading();
}

SensorPageHandler::SensorPageHandler(
    Profile* profile,
    ash::SensorProvider* provider,
    mojo::PendingReceiver<sensor::mojom::PageHandler> receiver)
    : SensorPageHandler(
          profile,
          provider,
          std::move(receiver),
          GetDownloadsDirectory(profile).Append("sensor_info.txt")) {}

SensorPageHandler::~SensorPageHandler() {
  provider_->StopSensorReading();
  provider_->RemoveObserver(this);
  ResetFile();
}

base::FilePath SensorPageHandler::GetDownloadsDirectory(Profile* profile) {
  const DownloadPrefs* const prefs = DownloadPrefs::FromBrowserContext(profile);
  base::FilePath path = prefs->DownloadPath();
  // Gets the DownloadsDirectory. Checks whether the given 'path' points to a
  // non-local filesystem that requires special handling.
  if (file_manager::util::IsUnderNonNativeLocalPath(profile, path)) {
    path = prefs->GetDefaultDownloadDirectoryForProfile();
  }
  return path;
}

void SensorPageHandler::OnSensorUpdated(const ash::SensorUpdate& update) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  std::vector<sensor::mojom::SensorUpdateInfoPtr> mojom_updates;
  for (int sensor_type = 0;
       sensor_type < static_cast<int>(ash::SensorType::kSensorTypeCount) - 1;
       ++sensor_type) {
    auto source = static_cast<ash::SensorType>(sensor_type);
    auto source_update = update.get(source);
    if (!source_update.has_value()) {
      continue;
    }

    if (source == ash::SensorType::kLidAngle) {
      mojom_updates.push_back(
          sensor::mojom::SensorUpdateInfo::NewLidAngleUpdateInfo(
              sensor::mojom::LidAngleUpdateInfo::New(
                  static_cast<sensor::mojom::SensorType>(source),
                  source_update->x)));
    } else {
      mojom_updates.push_back(sensor::mojom::SensorUpdateInfo::NewUpdateInfo(
          sensor::mojom::NonLidAngleUpdateInfo::New(
              static_cast<sensor::mojom::SensorType>(source), source_update->x,
              source_update->y, source_update->z)));
    }
  }
  // If saving is on and output file is opened.
  if (state_ == State::kOpened && save_update_ == true) {
    auto sensor_callback = base::BindOnce(
        [](const ash::SensorUpdate& update, base::File* file_ptr) {
          std::string update_output = GenerateString(update, base::Time::Now());
          if (file_ptr->WriteAtCurrentPosAndCheck(base::span<uint8_t>(
                  reinterpret_cast<uint8_t*>(update_output.data()),
                  update_output.size()))) {
            LOG(ERROR) << "Write Unsuccessful.";
          }
        },
        update, out_file_.get());
    task_runner_->PostTask(FROM_HERE, std::move(sensor_callback));
  }
}

std::string SensorPageHandler::GenerateString(const ash::SensorUpdate& update,
                                              base::Time time) {
  base::Time::Exploded exploded;
  time.UTCExplode(&exploded);
  std::string update_output = base::StringPrintf(
      "%04d-%02d-%02d %02d:%02d:%02d.%03d UTC\n", exploded.year, exploded.month,
      exploded.day_of_month, exploded.hour, exploded.minute, exploded.second,
      exploded.millisecond);

  for (int sensor_type = 0;
       sensor_type < static_cast<int>(ash::SensorType::kSensorTypeCount);
       ++sensor_type) {
    auto source = static_cast<ash::SensorType>(sensor_type);
    auto source_update = update.get(source);
    if (!source_update.has_value()) {
      update_output += "None.\n";
      continue;
    }

    if (source == ash::SensorType::kLidAngle) {
      update_output += base::StringPrintf("%.2f\n", source_update->x);
    } else {
      update_output += base::StringPrintf("%.2f %.2f %.2f\n", source_update->x,
                                          source_update->y, source_update->z);
    }
  }
  return update_output;
}

void SensorPageHandler::StartRecordingUpdate() {
  CHECK(!save_update_) << "Impossible state: open file already during open.";
  CHECK_NE(state_, State::kOpened)
      << "Impossible state: open file already during open.";
  save_update_ = true;
  if (state_ == State::kOpening) {
    // That there's an active file open in progress, and we should just switch
    // states and let it proceed.
    return;
  }
  state_ = State::kOpening;
  auto open_callback = base::BindOnce(
      [](const base::FilePath& filename) -> std::unique_ptr<base::File> {
        auto out_file = std::make_unique<base::File>(
            filename, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_APPEND);
        if (!out_file->IsValid()) {
          LOG(ERROR) << "Failed to create diagnostics log file " << filename;
          return nullptr;
        }
        return out_file;
      },
      out_file_path_);
  task_runner_->PostTaskAndReplyWithResult(
      FROM_HERE, std::move(open_callback),
      base::BindOnce(&SensorPageHandler::OnFileOpened,
                     weak_ptr_factory_.GetWeakPtr()));
}

void SensorPageHandler::OnFileOpened(std::unique_ptr<base::File> opened_file) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK_EQ(state_, State::kOpening)
      << "Impossible state: open file state should be kOpening.";
  out_file_ = std::move(opened_file);
  if (save_update_) {
    // Open received when 'save_update_' is true, that's fine. Lets set to open
    // and go.
    state_ = State::kOpened;
  } else {
    ResetFile();
  }
}

void SensorPageHandler::StopRecordingUpdate() {
  CHECK(save_update_) << "Impossible state: close file when already closed.";
  CHECK_NE(state_, State::kStopped)
      << "Impossible state: close file when already closed.";
  save_update_ = false;
  if (state_ == State::kOpening) {
    // a file open is in progress, let's switch to stopping state and leave.
    return;
  }
  ResetFile();
}

bool SensorPageHandler::CheckOutFileForTesting() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return out_file_.get();
}

void SensorPageHandler::ResetFile() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  state_ = State::kStopped;
  if (!out_file_) {
    return;
  }
  task_runner_->PostTask(FROM_HERE,
                         base::DoNothingWithBoundArgs(std::move(out_file_)));
}

}  // namespace ash