chromium/components/app_restore/restore_data.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 "components/app_restore/restore_data.h"

#include <utility>

#include "base/logging.h"
#include "base/not_fatal_until.h"
#include "base/strings/string_number_conversions.h"
#include "base/values.h"
#include "components/app_constants/constants.h"
#include "components/app_restore/app_launch_info.h"

namespace app_restore {

namespace {

// Used to generate unique restore window IDs for desk template launches. These
// IDs will all be negative in order to avoid clashes with full restore (which
// are all positive). The first generated ID will be one lower than the starting
// point and then proceed down. The starting point is a special case value that
// a valid RWID should not use.
int32_t g_desk_template_window_restore_id = -1;

// Key used to record `removing_desk_guid_` when `RestoreData` is converted to
// JSON.
constexpr char kRemovingDeskGuidKey[] = "removing_desk_guid";

}  // namespace

RestoreData::RestoreData() = default;

RestoreData::RestoreData(base::Value restore_data_value) {
  base::Value::Dict* dict = restore_data_value.GetIfDict();
  if (!dict) {
    DVLOG(0) << "Fail to parse full restore data. "
             << "Cannot find the full restore data dict.";
    return;
  }

  if (auto* removing_desk_guid_string =
          dict->FindString(kRemovingDeskGuidKey)) {
    removing_desk_guid_ =
        base::Uuid::ParseLowercase(*removing_desk_guid_string);
  }

  for (auto iter : *dict) {
    // `key` can be an app ID or `kRemovingDeskGuidKey`.
    const std::string& key = iter.first;
    base::Value::Dict* value = iter.second.GetIfDict();

    // Skip the removing desk GUID because we already covered this before the
    // loop.
    if (key == kRemovingDeskGuidKey) {
      continue;
    }

    if (!value) {
      DVLOG(0) << "Fail to parse full restore data. "
               << "Cannot find the app restore data dict.";
      continue;
    }

    for (auto data_iter : *value) {
      const std::string& window_id_string = data_iter.first;

      int window_id = 0;
      if (!base::StringToInt(window_id_string, &window_id)) {
        DVLOG(0) << "Fail to parse full restore data. "
                 << "Cannot find the valid id.";
        continue;
      }

      base::Value::Dict* app_restore_data_dict = data_iter.second.GetIfDict();
      if (!app_restore_data_dict) {
        DVLOG(0) << "Fail to parse app restore data. "
                 << "Cannot find the app restore data dict.";
        continue;
      }

      // If the data is for an app that was on a removing desk, then we can skip
      // adding the data.
      auto app_restore_data =
          std::make_unique<AppRestoreData>(std::move(*app_restore_data_dict));
      if (removing_desk_guid_.is_valid() &&
          app_restore_data->window_info.desk_guid == removing_desk_guid_) {
        continue;
      }

      app_id_to_launch_list_[key][window_id] = std::move(app_restore_data);
    }
  }
}

RestoreData::~RestoreData() = default;

std::unique_ptr<RestoreData> RestoreData::Clone() const {
  std::unique_ptr<RestoreData> restore_data = std::make_unique<RestoreData>();
  for (const auto& it : app_id_to_launch_list_) {
    for (const auto& data_it : it.second) {
      restore_data->app_id_to_launch_list_[it.first][data_it.first] =
          data_it.second->Clone();
    }
  }
  if (removing_desk_guid_.is_valid()) {
    restore_data->removing_desk_guid_ = removing_desk_guid_;
  }
  return restore_data;
}

base::Value RestoreData::ConvertToValue() const {
  base::Value::Dict restore_data_dict;
  for (const auto& [app_id, launch_list] : app_id_to_launch_list_) {
    if (launch_list.empty()) {
      continue;
    }

    base::Value::Dict info_dict;
    for (const auto& [window_id, app_restore_data] : launch_list) {
      info_dict.Set(base::NumberToString(window_id),
                    app_restore_data->ConvertToValue());
    }

    restore_data_dict.Set(app_id, std::move(info_dict));
  }

  if (removing_desk_guid_.is_valid()) {
    restore_data_dict.Set(kRemovingDeskGuidKey,
                          removing_desk_guid_.AsLowercaseString());
  }

  return base::Value(std::move(restore_data_dict));
}

bool RestoreData::HasAppTypeBrowser() const {
  auto it = app_id_to_launch_list_.find(app_constants::kChromeAppId);
  if (it == app_id_to_launch_list_.end())
    return false;

  return base::ranges::any_of(
      it->second,
      [](const std::pair<const int, std::unique_ptr<AppRestoreData>>& data) {
        return data.second->browser_extra_info.app_type_browser.value_or(false);
      });
}

bool RestoreData::HasBrowser() const {
  auto it = app_id_to_launch_list_.find(app_constants::kChromeAppId);
  if (it == app_id_to_launch_list_.end())
    return false;

  return base::ranges::any_of(
      it->second,
      [](const std::pair<const int, std::unique_ptr<AppRestoreData>>& data) {
        return !data.second->browser_extra_info.app_type_browser.value_or(
            false);
      });
}

bool RestoreData::HasAppRestoreData(const std::string& app_id,
                                    int32_t window_id) {
  return GetAppRestoreData(app_id, window_id) != nullptr;
}

void RestoreData::AddAppLaunchInfo(
    std::unique_ptr<AppLaunchInfo> app_launch_info) {
  if (!app_launch_info || !app_launch_info->window_id.has_value())
    return;

  const std::string app_id = app_launch_info->app_id;
  const int32_t window_id = app_launch_info->window_id.value();
  app_id_to_launch_list_[app_id][window_id] =
      std::make_unique<AppRestoreData>(std::move(app_launch_info));
}

void RestoreData::ModifyWindowId(const std::string& app_id,
                                 int32_t old_window_id,
                                 int32_t new_window_id) {
  auto it = app_id_to_launch_list_.find(app_id);
  if (it == app_id_to_launch_list_.end())
    return;

  auto data_it = it->second.find(old_window_id);
  if (data_it == it->second.end())
    return;

  it->second[new_window_id] = std::move(data_it->second);
  it->second.erase(data_it);
}

void RestoreData::ModifyWindowInfo(const std::string& app_id,
                                   int32_t window_id,
                                   const WindowInfo& window_info) {
  auto* app_restore_data = GetAppRestoreDataMutable(app_id, window_id);
  if (app_restore_data)
    app_restore_data->ModifyWindowInfo(window_info);
}

void RestoreData::ModifyThemeColor(const std::string& app_id,
                                   int32_t window_id,
                                   uint32_t primary_color,
                                   uint32_t status_bar_color) {
  auto* app_restore_data = GetAppRestoreDataMutable(app_id, window_id);
  if (app_restore_data)
    app_restore_data->ModifyThemeColor(primary_color, status_bar_color);
}

void RestoreData::SetNextRestoreWindowIdForChromeApp(
    const std::string& app_id) {
  auto it = app_id_to_launch_list_.find(app_id);
  if (it == app_id_to_launch_list_.end())
    return;

  chrome_app_id_to_current_window_id_[app_id] = it->second.begin()->first;

  if (it->second.size() == 1)
    return;

  // When a chrome app has multiple windows, all windows will be sent to the
  // background.
  for (auto& [window_id, app_restore_data] : it->second) {
    app_restore_data->window_info.activation_index = INT32_MAX;
  }
}

void RestoreData::RemoveAppRestoreData(const std::string& app_id,
                                       int window_id) {
  if (app_id_to_launch_list_.find(app_id) == app_id_to_launch_list_.end())
    return;

  app_id_to_launch_list_[app_id].erase(window_id);
  if (app_id_to_launch_list_[app_id].empty())
    app_id_to_launch_list_.erase(app_id);
}

void RestoreData::SendWindowToBackground(const std::string& app_id,
                                         int window_id) {
  if (auto* app_restore_data = GetAppRestoreDataMutable(app_id, window_id)) {
    app_restore_data->window_info.activation_index = INT32_MAX;
  }
}

void RestoreData::RemoveApp(const std::string& app_id) {
  app_id_to_launch_list_.erase(app_id);
  chrome_app_id_to_current_window_id_.erase(app_id);
}

std::unique_ptr<AppLaunchInfo> RestoreData::GetAppLaunchInfo(
    const std::string& app_id,
    int window_id) {
  auto* app_restore_data = GetAppRestoreData(app_id, window_id);
  return app_restore_data
             ? app_restore_data->GetAppLaunchInfo(app_id, window_id)
             : nullptr;
}

std::unique_ptr<WindowInfo> RestoreData::GetWindowInfo(
    const std::string& app_id,
    int window_id) {
  auto* app_restore_data = GetAppRestoreData(app_id, window_id);
  return app_restore_data ? app_restore_data->GetWindowInfo() : nullptr;
}

int32_t RestoreData::FetchRestoreWindowId(const std::string& app_id) {
  auto it = app_id_to_launch_list_.find(app_id);
  if (it == app_id_to_launch_list_.end())
    return 0;

  if (chrome_app_id_to_current_window_id_.find(app_id) ==
      chrome_app_id_to_current_window_id_.end()) {
    return 0;
  }

  int window_id = chrome_app_id_to_current_window_id_[app_id];

  // Move to the next window_id.
  auto data_it = it->second.find(window_id);
  CHECK(data_it != it->second.end(), base::NotFatalUntil::M130);
  ++data_it;
  if (data_it == it->second.end())
    chrome_app_id_to_current_window_id_.erase(app_id);
  else
    chrome_app_id_to_current_window_id_[app_id] = data_it->first;

  return window_id;
}

const AppRestoreData* RestoreData::GetAppRestoreData(const std::string& app_id,
                                                     int window_id) const {
  auto it = app_id_to_launch_list_.find(app_id);
  if (it == app_id_to_launch_list_.end())
    return nullptr;

  auto data_it = it->second.find(window_id);
  if (data_it == it->second.end())
    return nullptr;

  return data_it->second.get();
}

void RestoreData::SetDeskUuid(const base::Uuid& desk_uuid) {
  for (auto& [app_id, launch_list] : app_id_to_launch_list_) {
    for (auto& [window_id, app_restore_data] : launch_list) {
      app_restore_data->window_info.desk_guid = desk_uuid;
    }
  }
}

base::flat_map<int32_t, int32_t>
RestoreData::MakeWindowIdsUniqueForDeskTemplate() {
  if (has_unique_window_ids_for_desk_template_) {
    return {};
  }

  base::flat_map<int32_t, int32_t> mapping;
  for (auto& [app_id, launch_list] : app_id_to_launch_list_) {
    // We don't want to do in-place updates of the launch list since it
    // complicates traversal. We'll therefore build a new LaunchList and pilfer
    // the old one for AppRestoreData.
    LaunchList new_launch_list;
    for (auto& [window_id, app_restore_data] : launch_list) {
      int32_t new_rwid = --g_desk_template_window_restore_id;
      new_launch_list[new_rwid] = std::move(app_restore_data);
      mapping[new_rwid] = window_id;
    }
    launch_list = std::move(new_launch_list);
  }

  has_unique_window_ids_for_desk_template_ = true;
  return mapping;
}

void RestoreData::UpdateBrowserAppIdToLacros() {
  auto app_launch_list_iter =
      app_id_to_launch_list_.find(app_constants::kChromeAppId);
  if (app_launch_list_iter == app_id_to_launch_list_.end()) {
    return;
  }
  app_id_to_launch_list_[app_constants::kLacrosAppId] =
      std::move(app_launch_list_iter->second);
  RemoveApp(app_constants::kChromeAppId);
}

std::string RestoreData::ToString() const {
  return ConvertToValue().DebugString();
}

AppRestoreData* RestoreData::GetAppRestoreDataMutable(const std::string& app_id,
                                                      int window_id) {
  return const_cast<AppRestoreData*>(GetAppRestoreData(app_id, window_id));
}

}  // namespace app_restore