chromium/chrome/browser/ui/views/crostini/crostini_recovery_view.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/views/crostini/crostini_recovery_view.h"

#include "base/metrics/histogram_functions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "chrome/browser/ash/crostini/crostini_features.h"
#include "chrome/browser/ash/crostini/crostini_manager.h"
#include "chrome/browser/ash/crostini/crostini_util.h"
#include "chrome/browser/ash/guest_os/guest_os_terminal.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/mojom/dialog_button.mojom.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/chromeos/devicetype_utils.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/border.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/layout_provider.h"

namespace {

CrostiniRecoveryView* g_crostini_recovery_view = nullptr;

constexpr char kCrostiniRecoverySourceHistogram[] = "Crostini.RecoverySource";

}  // namespace

void crostini::ShowCrostiniRecoveryView(
    Profile* profile,
    crostini::CrostiniUISurface ui_surface,
    const std::string& app_id,
    int64_t display_id,
    const std::vector<guest_os::LaunchArg>& args,
    crostini::CrostiniSuccessCallback callback) {
  CrostiniRecoveryView::Show(profile, app_id, display_id, args,
                             std::move(callback));
  base::UmaHistogramEnumeration(kCrostiniRecoverySourceHistogram, ui_surface,
                                crostini::CrostiniUISurface::kCount);
}

void CrostiniRecoveryView::Show(Profile* profile,
                                const std::string& app_id,
                                int64_t display_id,
                                const std::vector<guest_os::LaunchArg>& args,
                                crostini::CrostiniSuccessCallback callback) {
  if (!crostini::CrostiniFeatures::Get()->IsAllowedNow(profile)) {
    std::move(callback).Run(false, "crostini is not allowed");
    return;
  }

  // Any new apps launched during recovery are immediately cancelled.
  if (g_crostini_recovery_view) {
    std::move(callback).Run(false, "recovery in progress");
  } else {
    g_crostini_recovery_view = new CrostiniRecoveryView(
        profile, app_id, display_id, args, std::move(callback));
    CreateDialogWidget(g_crostini_recovery_view, nullptr, nullptr);
  }
  // Always call Show to bring the dialog to the front of the screen.
  g_crostini_recovery_view->GetWidget()->Show();
}

bool CrostiniRecoveryView::Accept() {
  SetButtonEnabled(ui::mojom::DialogButton::kOk, false);
  SetButtonEnabled(ui::mojom::DialogButton::kCancel, false);
  crostini::CrostiniManager::GetForProfile(profile_)->StopVm(
      crostini::kCrostiniDefaultVmName,
      base::BindOnce(&CrostiniRecoveryView::OnStopVm,
                     weak_ptr_factory_.GetWeakPtr()));
  DialogModelChanged();
  return false;
}

void CrostiniRecoveryView::OnStopVm(crostini::CrostiniResult result) {
  VLOG(1) << "Scheduling app launch " << app_id_;
  if (result != crostini::CrostiniResult::SUCCESS) {
    LOG(ERROR) << "Error stopping VM for recovery: " << (int)result;
  }
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&crostini::LaunchCrostiniApp, profile_, app_id_,
                                display_id_, args_, std::move(callback_)));
  GetWidget()->CloseWithReason(
      views::Widget::ClosedReason::kAcceptButtonClicked);
}

bool CrostiniRecoveryView::Cancel() {
  if (callback_) {
    std::move(callback_).Run(false, "cancelled for recovery");
    guest_os::LaunchTerminal(profile_, display_id_,
                             crostini::DefaultContainerId());
  }
  return true;
}

// static
CrostiniRecoveryView* CrostiniRecoveryView::GetActiveViewForTesting() {
  return g_crostini_recovery_view;
}

CrostiniRecoveryView::CrostiniRecoveryView(
    Profile* profile,
    const std::string& app_id,
    int64_t display_id,
    const std::vector<guest_os::LaunchArg>& args,
    crostini::CrostiniSuccessCallback callback)
    : profile_(profile),
      app_id_(app_id),
      display_id_(display_id),
      args_(args),
      callback_(std::move(callback)),
      weak_ptr_factory_(this) {
  SetButtons(static_cast<int>(ui::mojom::DialogButton::kOk) |
             static_cast<int>(ui::mojom::DialogButton::kCancel));
  SetButtonLabel(
      ui::mojom::DialogButton::kOk,
      l10n_util::GetStringUTF16(IDS_CROSTINI_RECOVERY_RESTART_BUTTON));
  SetButtonLabel(
      ui::mojom::DialogButton::kCancel,
      l10n_util::GetStringUTF16(IDS_CROSTINI_RECOVERY_TERMINAL_BUTTON));
  SetShowCloseButton(false);
  SetTitle(IDS_CROSTINI_RECOVERY_TITLE);
  set_fixed_width(ChromeLayoutProvider::Get()->GetDistanceMetric(
      DISTANCE_STANDALONE_BUBBLE_PREFERRED_WIDTH));

  views::LayoutProvider* provider = views::LayoutProvider::Get();
  SetLayoutManager(std::make_unique<views::BoxLayout>(
      views::BoxLayout::Orientation::kVertical,
      provider->GetInsetsMetric(views::InsetsMetric::INSETS_DIALOG),
      provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL)));

  const std::u16string message =
      l10n_util::GetStringUTF16(IDS_CROSTINI_RECOVERY_MESSAGE);
  views::Label* message_label = new views::Label(message);
  message_label->SetMultiLine(true);
  message_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  AddChildView(message_label);
}

CrostiniRecoveryView::~CrostiniRecoveryView() {
  g_crostini_recovery_view = nullptr;
}

BEGIN_METADATA(CrostiniRecoveryView)
END_METADATA