chromium/chrome/browser/ui/webui/ash/arc_power_control/arc_power_control_handler.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/ui/webui/ash/arc_power_control/arc_power_control_handler.h"

#include "ash/components/arc/mojom/power.mojom.h"
#include "ash/components/arc/session/arc_bridge_service.h"
#include "ash/components/arc/session/arc_service_manager.h"
#include "base/functional/bind.h"
#include "base/linux_util.h"
#include "base/process/launch.h"
#include "base/task/thread_pool.h"
#include "base/trace_event/trace_event.h"
#include "base/values.h"
#include "chrome/browser/ash/arc/instance_throttle/arc_instance_throttle.h"
#include "chrome/browser/ash/arc/tracing/arc_system_stat_collector.h"
#include "chrome/browser/ash/arc/tracing/arc_tracing_graphics_model.h"
#include "chrome/browser/ash/arc/tracing/arc_tracing_model.h"
#include "chrome/browser/ash/arc/tracing/arc_value_event_trimmer.h"
#include "chrome/browser/ash/arc/tracing/present_frames_tracer.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"

namespace ash {

namespace {

// Maximum interval to display in tracing.
constexpr base::TimeDelta kMaxIntervalToDisplay = base::Minutes(5);

// Names of throttling mode.
constexpr char kThrottlingDisable[] = "disable";
constexpr char kThrottlingAuto[] = "auto";
constexpr char kThrottlingForce[] = "force";

// Names of wakenessfull mode.
constexpr char kWakenessfullWakeUp[] = "wakeup";
constexpr char kWakenessfullDoze[] = "doze";
constexpr char kWakenessfullForceDoze[] = "force-doze";
constexpr char kWakenessfullSleep[] = "sleep";

// To read developer mode.
constexpr const char kCrosSystemPath[] = "/usr/bin/crossystem";
constexpr const char kCrosDebug[] = "cros_debug";

void OnAndroidSuspendReady() {}

std::string GetJavascriptDomain() {
  return "cr.ArcPowerControl.";
}

bool IsDeveloperMode() {
  std::string output;
  if (!base::GetAppOutput({kCrosSystemPath, kCrosDebug}, &output)) {
    LOG(ERROR) << "Failed to read property " << kCrosDebug;
    return false;
  }

  return output == "1";
}

base::Value BuildTracingModel(
    base::Time timestamp,
    base::TimeTicks time_min,
    base::TimeTicks time_max,
    std::unique_ptr<arc::ArcSystemStatCollector> system_stat_collector,
    ArcPowerControlHandler::WakefulnessModeEvents wakefulness_mode_events,
    ArcPowerControlHandler::ThrottlingEvents throttling_events) {
  DCHECK(system_stat_collector);

  arc::ArcTracingModel common_model;
  const base::TimeTicks time_min_clamped =
      std::max(time_min, time_max - system_stat_collector->max_interval());
  common_model.SetMinMaxTime(
      (time_min_clamped - base::TimeTicks()).InMicroseconds(),
      (time_max - base::TimeTicks()).InMicroseconds());

  arc::ArcValueEventTrimmer wakenessfull_mode(
      &common_model.system_model().memory_events(),
      arc::ArcValueEvent::Type::kWakenessfullMode);
  for (const auto& wakefulness_mode_event : wakefulness_mode_events) {
    const int64_t wakefulness_mode_timestamp =
        (wakefulness_mode_event.first - base::TimeTicks()).InMicroseconds();
    wakenessfull_mode.MaybeAdd(wakefulness_mode_timestamp,
                               static_cast<int>(wakefulness_mode_event.second));
  }
  arc::ArcValueEventTrimmer throttling(
      &common_model.system_model().memory_events(),
      arc::ArcValueEvent::Type::kThrottlingMode);
  for (const auto& throttling_event : throttling_events) {
    const int64_t throttling_timestamp =
        (throttling_event.first - base::TimeTicks()).InMicroseconds();
    throttling.MaybeAdd(throttling_timestamp,
                        static_cast<int>(throttling_event.second));
  }

  // Flush automatically normalizes the model.
  system_stat_collector->Flush(time_min, time_max,
                               &common_model.system_model());

  arc::ArcTracingGraphicsModel graphics_model;
  graphics_model.set_skip_structure_validation();
  graphics_model.set_platform(base::GetLinuxDistro());
  graphics_model.set_timestamp(timestamp);
  graphics_model.Build(common_model, arc::PresentFramesTracer() /* commits */);

  return base::Value(graphics_model.Serialize());
}

}  // namespace

ArcPowerControlHandler::ArcPowerControlHandler()
    : power_bridge_(arc::ArcPowerBridge::GetForBrowserContext(
          ProfileManager::GetActiveUserProfile())),
      instance_throttle_(arc::ArcInstanceThrottle::GetForBrowserContext(
          ProfileManager::GetActiveUserProfile())) {
  DCHECK(power_bridge_);
  DCHECK(instance_throttle_);
  power_bridge_->AddObserver(this);
  instance_throttle_->AddServiceObserver(this);
}

ArcPowerControlHandler::~ArcPowerControlHandler() {
  instance_throttle_->RemoveServiceObserver(this);
  power_bridge_->RemoveObserver(this);
}

void ArcPowerControlHandler::RegisterMessages() {
  web_ui()->RegisterMessageCallback(
      "ready", base::BindRepeating(&ArcPowerControlHandler::HandleReady,
                                   base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "setWakefulnessMode",
      base::BindRepeating(&ArcPowerControlHandler::HandleSetWakefulnessMode,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "setThrottling",
      base::BindRepeating(&ArcPowerControlHandler::HandleSetThrottling,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "startTracing",
      base::BindRepeating(&ArcPowerControlHandler::HandleStartTracing,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "stopTracing",
      base::BindRepeating(&ArcPowerControlHandler::HandleStopTracing,
                          base::Unretained(this)));
}

void ArcPowerControlHandler::OnWakefulnessChanged(
    arc::mojom::WakefulnessMode mode) {
  wakefulness_mode_ = mode;
  UpdatePowerControlStatus();

  if (stop_tracing_timer_.IsRunning()) {
    wakefulness_mode_events_.emplace_back(
        std::make_pair(TRACE_TIME_TICKS_NOW(), wakefulness_mode_));
  }
}

void ArcPowerControlHandler::OnThrottle(bool throttled) {
  UpdatePowerControlStatus();

  std::string mode = kThrottlingAuto;
  auto* observer = instance_throttle_->GetObserverByName(
      arc::ArcInstanceThrottle::kChromeArcPowerControlPageObserver);
  if (observer && observer->enforced())
    mode = observer->active() ? kThrottlingDisable : kThrottlingForce;

  CallJavascriptFunction(GetJavascriptDomain() + "setThrottlingMode",
                         base::Value(mode));

  if (stop_tracing_timer_.IsRunning()) {
    throttling_events_.emplace_back(
        std::make_pair(TRACE_TIME_TICKS_NOW(), throttled));
  }
}

void ArcPowerControlHandler::HandleReady(const base::Value::List& args) {
  arc::mojom::PowerInstance* power_instance = ARC_GET_INSTANCE_FOR_METHOD(
      arc::ArcServiceManager::Get()->arc_bridge_service()->power(),
      GetWakefulnessMode);
  if (!power_instance)
    return;

  power_instance->GetWakefulnessMode(
      base::BindOnce(&ArcPowerControlHandler::OnWakefulnessChanged,
                     weak_ptr_factory_.GetWeakPtr()));

  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
      base::BindOnce(&IsDeveloperMode),
      base::BindOnce(&ArcPowerControlHandler::OnIsDeveloperMode,
                     weak_ptr_factory_.GetWeakPtr()));

  OnThrottle(instance_throttle_->should_throttle());
}

void ArcPowerControlHandler::HandleSetWakefulnessMode(
    const base::Value::List& args) {
  DCHECK_EQ(1U, args.size());

  if (!power_control_enabled_) {
    LOG(ERROR) << "Power control is not enabled";
    return;
  }

  if (!args[0].is_string()) {
    LOG(ERROR) << "Invalid input";
    return;
  }

  auto* const power =
      arc::ArcServiceManager::Get()->arc_bridge_service()->power();
  DCHECK(power);

  const std::string mode = args[0].GetString();
  if (mode == kWakenessfullWakeUp) {
    if (wakefulness_mode_ == arc::mojom::WakefulnessMode::ASLEEP) {
      arc::mojom::PowerInstance* const power_instance =
          ARC_GET_INSTANCE_FOR_METHOD(power, Resume);
      if (power_instance)
        power_instance->Resume();
    } else {
      arc::mojom::PowerInstance* const power_instance =
          ARC_GET_INSTANCE_FOR_METHOD(power, SetIdleState);
      if (power_instance)
        power_instance->SetIdleState(arc::mojom::IdleState::ACTIVE);
    }
  } else if (mode == kWakenessfullDoze) {
    arc::mojom::PowerInstance* const power_instance =
        ARC_GET_INSTANCE_FOR_METHOD(power, SetIdleState);
    if (power_instance)
      power_instance->SetIdleState(arc::mojom::IdleState::INACTIVE);
  } else if (mode == kWakenessfullForceDoze) {
    arc::mojom::PowerInstance* const power_instance =
        ARC_GET_INSTANCE_FOR_METHOD(power, SetIdleState);
    if (power_instance) {
      power_instance->SetIdleState(arc::mojom::IdleState::FORCE_INACTIVE);
    }
  } else if (mode == kWakenessfullSleep) {
    arc::mojom::PowerInstance* const power_instance =
        ARC_GET_INSTANCE_FOR_METHOD(power, Suspend);
    if (power_instance)
      power_instance->Suspend(base::BindOnce(&OnAndroidSuspendReady));
  } else {
    LOG(ERROR) << "Invalid mode: " << mode;
  }
}

void ArcPowerControlHandler::HandleSetThrottling(
    const base::Value::List& args) {
  DCHECK_EQ(1U, args.size());

  if (!power_control_enabled_) {
    LOG(ERROR) << "Power control is not enabled";
    return;
  }

  if (!args[0].is_string()) {
    LOG(ERROR) << "Invalid input";
    return;
  }

  auto* observer = instance_throttle_->GetObserverByName(
      arc::ArcInstanceThrottle::kChromeArcPowerControlPageObserver);
  if (!observer) {
    LOG(ERROR)
        << "An throttle observer for chrome://arc-power-control not found";
    return;
  }

  const std::string mode = args[0].GetString();
  if (mode == kThrottlingDisable) {
    observer->SetActive(true);
    observer->SetEnforced(true);
  } else if (mode == kThrottlingAuto) {
    observer->SetActive(false);
    observer->SetEnforced(false);
  } else if (mode == kThrottlingForce) {
    observer->SetActive(false);
    observer->SetEnforced(true);
  } else {
    LOG(ERROR) << "Invalid mode: " << mode;
    return;
  }
}

void ArcPowerControlHandler::HandleStartTracing(const base::Value::List& args) {
  DCHECK(!args.size());
  StartTracing();
}

void ArcPowerControlHandler::HandleStopTracing(const base::Value::List& args) {
  DCHECK(!args.size());
  StopTracing();
}

void ArcPowerControlHandler::StartTracing() {
  SetTracingStatus("Collecting samples...");

  timestamp_ = base::Time::Now();
  tracing_time_min_ = TRACE_TIME_TICKS_NOW();

  wakefulness_mode_events_.clear();
  wakefulness_mode_events_.emplace_back(
      std::make_pair(tracing_time_min_, wakefulness_mode_));
  throttling_events_.clear();
  throttling_events_.emplace_back(
      std::make_pair(tracing_time_min_, instance_throttle_->should_throttle()));
  system_stat_collector_ = std::make_unique<arc::ArcSystemStatCollector>();
  system_stat_collector_->Start(kMaxIntervalToDisplay);
  stop_tracing_timer_.Start(FROM_HERE, system_stat_collector_->max_interval(),
                            base::BindOnce(&ArcPowerControlHandler::StopTracing,
                                           base::Unretained(this)));
}

void ArcPowerControlHandler::StopTracing() {
  if (!system_stat_collector_)
    return;

  const base::TimeTicks tracing_time_max = TRACE_TIME_TICKS_NOW();
  stop_tracing_timer_.Stop();
  system_stat_collector_->Stop();

  SetTracingStatus("Building model...");

  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
      base::BindOnce(&BuildTracingModel, timestamp_, tracing_time_min_,
                     tracing_time_max, std::move(system_stat_collector_),
                     std::move(wakefulness_mode_events_),
                     std::move(throttling_events_)),
      base::BindOnce(&ArcPowerControlHandler::OnTracingModelReady,
                     weak_ptr_factory_.GetWeakPtr()));
}

void ArcPowerControlHandler::OnTracingModelReady(base::Value result) {
  SetTracingStatus("Tracing ready");

  CallJavascriptFunction(GetJavascriptDomain() + "setModel", std::move(result));
}

void ArcPowerControlHandler::OnIsDeveloperMode(bool developer_mode) {
  power_control_enabled_ = developer_mode;
  CallJavascriptFunction(GetJavascriptDomain() + "setPowerControlEnabled",
                         base::Value(power_control_enabled_));
}

void ArcPowerControlHandler::UpdatePowerControlStatus() {
  AllowJavascript();

  std::string status;
  switch (wakefulness_mode_) {
    case arc::mojom::WakefulnessMode::UNKNOWN:
      status = "Unknown";
      break;
    case arc::mojom::WakefulnessMode::ASLEEP:
      status = "Asleep";
      break;
    case arc::mojom::WakefulnessMode::AWAKE:
      status = "Awake";
      break;
    case arc::mojom::WakefulnessMode::DREAMING:
      status = "Dreaming";
      break;
    case arc::mojom::WakefulnessMode::DOZING:
      status = "Dozing";
      break;
  }

  status += " (";
  if (instance_throttle_->should_throttle())
    status += "throttling";
  else
    status += "critical foreground";
  status += ")";

  CallJavascriptFunction(GetJavascriptDomain() + "setPowerControlStatus",
                         base::Value(status));
}

void ArcPowerControlHandler::SetTracingStatus(const std::string& status) {
  CallJavascriptFunction(GetJavascriptDomain() + "setTracingStatus",
                         base::Value(status));
}

}  // namespace ash