chromium/chrome/browser/ash/crosapi/arc_ash.cc

// Copyright 2021 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/ash/crosapi/arc_ash.h"

#include <utility>

#include "ash/components/arc/mojom/scale_factor.mojom.h"
#include "ash/components/arc/session/arc_bridge_service.h"
#include "ash/components/arc/session/arc_service_manager.h"
#include "base/barrier_closure.h"
#include "base/functional/bind.h"
#include "chrome/browser/apps/app_service/app_icon/app_icon_factory.h"
#include "chrome/browser/ash/app_list/arc/arc_app_list_prefs.h"
#include "chrome/browser/profiles/profile.h"
#include "components/arc/intent_helper/arc_intent_helper_bridge.h"

// Check ScaleFactor values are the same for arc::mojom and crosapi::mojom.
#define STATIC_ASSERT_SCALE_FACTOR(v)                               \
  static_assert(static_cast<int>(crosapi::mojom::ScaleFactor::v) == \
                    static_cast<int>(arc::mojom::ScaleFactor::v),   \
                "mismatching enums: " #v)
STATIC_ASSERT_SCALE_FACTOR(SCALE_FACTOR_NONE);
STATIC_ASSERT_SCALE_FACTOR(SCALE_FACTOR_100P);
STATIC_ASSERT_SCALE_FACTOR(SCALE_FACTOR_200P);
STATIC_ASSERT_SCALE_FACTOR(SCALE_FACTOR_300P);

namespace crosapi {

namespace {

// Used for converting RawIconPngData to ImageSkia.
// These petemeters must be consistent with SmartSelection's icon configuration.
constexpr size_t kBytesPerPixel = 4;  // BGRA
constexpr size_t kSmallIconSizeInDip = 16;
constexpr size_t kMaxIconSizeInPx = 200;

// Retrurns IntentHelperHolder for getting mojom API.
// Return nullptr if not ready or supported.
arc::ConnectionHolder<arc::mojom::IntentHelperInstance,
                      arc::mojom::IntentHelperHost>*
GetIntentHelperHolder() {
  auto* arc_service_manager = arc::ArcServiceManager::Get();
  if (!arc_service_manager) {
    LOG(WARNING) << "ARC is not ready";
    return nullptr;
  }

  auto* intent_helper_holder =
      arc_service_manager->arc_bridge_service()->intent_helper();
  if (!intent_helper_holder->IsConnected()) {
    LOG(WARNING) << "ARC intent helper instance is not ready.";
    return nullptr;
  }

  return intent_helper_holder;
}

mojom::ActivityNamePtr ConvertArcActivityName(
    arc::mojom::ActivityNamePtr activity) {
  return mojom::ActivityName::New(activity->package_name,
                                  activity->activity_name);
}

mojom::IntentInfoPtr ConvertArcIntentInfo(arc::mojom::IntentInfoPtr intent) {
  return mojom::IntentInfo::New(intent->action, intent->categories,
                                intent->data, intent->type, intent->ui_bypassed,
                                intent->extras);
}

void OnIsInstallable(mojom::Arc::IsInstallableCallback callback,
                     bool installable) {
  std::move(callback).Run(
      installable ? crosapi::mojom::IsInstallableResult::kInstallable
                  : crosapi::mojom::IsInstallableResult::kNotInstallable);
}

}  // namespace

ArcAsh::ArcAsh() = default;

ArcAsh::~ArcAsh() = default;

void ArcAsh::MaybeSetProfile(Profile* profile) {
  CHECK(profile);
  if (profile_) {
    VLOG(1) << "ArcAsh service is already initialized. Skip init.";
    return;
  }

  profile_ = profile;
  auto* bridge = arc::ArcIntentHelperBridge::GetForBrowserContext(profile_);
  if (bridge) {
    bridge->AddObserver(this);
  }
  profile_observation_.Observe(profile_);
}

void ArcAsh::BindReceiver(mojo::PendingReceiver<mojom::Arc> receiver) {
  // profile_ should be set beforehand.
  DCHECK(profile_);
  receivers_.Add(this, std::move(receiver));
}

void ArcAsh::AddObserver(mojo::PendingRemote<mojom::ArcObserver> observer) {
  mojo::Remote<mojom::ArcObserver> remote(std::move(observer));
  observers_.Add(std::move(remote));
}

void ArcAsh::OnArcIntentHelperBridgeShutdown(
    arc::ArcIntentHelperBridge* bridge) {
  // Remove observers here instead of ~ArcAsh() since ArcIntentHelperBridge
  // is shut down before ~ArcAsh() is called.
  // Both of them are destroyed in
  // ChromeBrowserMainPartsAsh::PostMainMessageLoopRun(), but
  // ArcIntentHelperBridge is shut down and destroyed in
  // ChromeBrowserMainPartsLinux::PostMainMessageLoopRun() while ArcAsh is
  // destroyed in crosapi_manager_.reset() which runs later.
  if (bridge) {
    bridge->RemoveObserver(this);
  }
}

void ArcAsh::RequestActivityIcons(
    std::vector<mojom::ActivityNamePtr> activities,
    mojom::ScaleFactor scale_factor,
    RequestActivityIconsCallback callback) {
  auto* intent_helper_holder = GetIntentHelperHolder();
  if (!intent_helper_holder) {
    std::move(callback).Run(
        std::vector<mojom::ActivityIconPtr>(),
        mojom::RequestActivityIconsStatus::kArcNotAvailable);
    return;
  }

  auto* instance =
      ARC_GET_INSTANCE_FOR_METHOD(intent_helper_holder, RequestActivityIcons);
  if (!instance) {
    LOG(WARNING) << "RequestActivityIcons is not supported.";
    std::move(callback).Run(
        std::vector<mojom::ActivityIconPtr>(),
        mojom::RequestActivityIconsStatus::kArcNotAvailable);
    return;
  }

  // Convert activities to arc::mojom::ActivityNamePtr from
  // crosapi::mojom::ActivityNamePtr.
  std::vector<arc::mojom::ActivityNamePtr> converted_activities;
  converted_activities.reserve(activities.size());
  for (const auto& activity : activities) {
    converted_activities.push_back(arc::mojom::ActivityName::New(
        activity->package_name, activity->activity_name));
  }
  instance->RequestActivityIcons(
      std::move(converted_activities), arc::mojom::ScaleFactor(scale_factor),
      base::BindOnce(&ArcAsh::ConvertActivityIcons,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void ArcAsh::ConvertActivityIcons(
    RequestActivityIconsCallback callback,
    std::vector<arc::mojom::ActivityIconPtr> icons) {
  // Convert icons to crosapi::mojom::ActivityIconPtr from
  // arc::mojom::ActivityIconPtr.
  std::vector<mojom::ActivityIconPtr> converted_icons;
  converted_icons.reserve(icons.size());
  for (const auto& icon : icons) {
    converted_icons.push_back(mojom::ActivityIcon::New(
        mojom::ActivityName::New(icon->activity->package_name,
                                 icon->activity->activity_name),
        icon->width, icon->height, icon->icon,
        mojom::RawIconPngData::New(
            icon->icon_png_data->is_adaptive_icon,
            icon->icon_png_data->icon_png_data,
            icon->icon_png_data->foreground_icon_png_data,
            icon->icon_png_data->background_icon_png_data)));
  }
  std::move(callback).Run(std::move(converted_icons),
                          mojom::RequestActivityIconsStatus::kSuccess);
}

void ArcAsh::RequestUrlHandlerList(const std::string& url,
                                   RequestUrlHandlerListCallback callback) {
  auto* intent_helper_holder = GetIntentHelperHolder();
  if (!intent_helper_holder) {
    std::move(callback).Run(
        std::vector<mojom::IntentHandlerInfoPtr>(),
        mojom::RequestUrlHandlerListStatus::kArcNotAvailable);
    return;
  }

  auto* instance =
      ARC_GET_INSTANCE_FOR_METHOD(intent_helper_holder, RequestUrlHandlerList);
  if (!instance) {
    LOG(WARNING) << "RequestUrlHandlerList is not supported.";
    std::move(callback).Run(
        std::vector<mojom::IntentHandlerInfoPtr>(),
        mojom::RequestUrlHandlerListStatus::kArcNotAvailable);
    return;
  }

  instance->RequestUrlHandlerList(
      url, base::BindOnce(&ArcAsh::ConvertIntentHandlerInfo,
                          weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void ArcAsh::ConvertIntentHandlerInfo(
    RequestUrlHandlerListCallback callback,
    std::vector<arc::mojom::IntentHandlerInfoPtr> handlers) {
  // Convert handlers to crosapi::mojom::IntentHandlerInfoPtr from
  // arc::mojom::IntentHandlerInfoPtr.
  std::vector<mojom::IntentHandlerInfoPtr> converted_handlers;
  for (const auto& handler : handlers) {
    mojom::IntentHandlerInfoPtr converted_handler(mojom::IntentHandlerInfo::New(
        handler->name, handler->package_name, handler->activity_name));
    converted_handlers.push_back(std::move(converted_handler));
  }
  std::move(callback).Run(std::move(converted_handlers),
                          mojom::RequestUrlHandlerListStatus::kSuccess);
}

void ArcAsh::RequestTextSelectionActions(
    const std::string& text,
    mojom::ScaleFactor scale_factor,
    RequestTextSelectionActionsCallback callback) {
  auto* intent_helper_holder = GetIntentHelperHolder();
  if (!intent_helper_holder) {
    std::move(callback).Run(
        mojom::RequestTextSelectionActionsStatus::kArcNotAvailable,
        std::vector<mojom::TextSelectionActionPtr>());
    return;
  }

  auto* instance = ARC_GET_INSTANCE_FOR_METHOD(intent_helper_holder,
                                               RequestTextSelectionActions);
  if (!instance) {
    LOG(WARNING) << "RequestTextSelectionActions is not supported.";
    std::move(callback).Run(
        mojom::RequestTextSelectionActionsStatus::kArcNotAvailable,
        std::vector<mojom::TextSelectionActionPtr>());
    return;
  }

  instance->RequestTextSelectionActions(
      text, arc::mojom::ScaleFactor(scale_factor),
      base::BindOnce(&ArcAsh::ConvertTextSelectionActions,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void ArcAsh::ConvertTextSelectionActions(
    RequestTextSelectionActionsCallback callback,
    std::vector<arc::mojom::TextSelectionActionPtr> actions) {
  size_t actions_count = actions.size();
  auto converted_actions =
      std::vector<mojom::TextSelectionActionPtr>(actions_count);
  auto* converted_actions_ptr = converted_actions.data();

  base::RepeatingClosure barrier_closure = base::BarrierClosure(
      actions_count,
      base::BindOnce(
          [](std::vector<mojom::TextSelectionActionPtr> actions,
             RequestTextSelectionActionsCallback cb) {
            std::move(cb).Run(
                mojom::RequestTextSelectionActionsStatus::kSuccess,
                std::move(actions));
          },
          std::move(converted_actions), std::move(callback)));

  for (size_t idx = 0; idx < actions_count; ++idx) {
    auto action = std::move(actions[idx]);
    auto* converted_action = &converted_actions_ptr[idx];

    // If actions[idx]->icon doesn't meet the size condition, skip generating
    // image.
    if (action->icon->width > kMaxIconSizeInPx ||
        action->icon->height > kMaxIconSizeInPx || action->icon->width == 0 ||
        action->icon->height == 0 ||
        action->icon->icon.size() !=
            (action->icon->width * action->icon->height * kBytesPerPixel)) {
      ConvertTextSelectionAction(converted_action, std::move(action),
                                 barrier_closure, gfx::ImageSkia());
      continue;
    }

    // Generate ImageSkia icon.
    auto icon_png_data = std::move(action->icon->icon_png_data);
    apps::ArcRawIconPngDataToImageSkia(
        std::move(icon_png_data), kSmallIconSizeInDip,
        base::BindOnce(&ArcAsh::ConvertTextSelectionAction,
                       weak_ptr_factory_.GetWeakPtr(), converted_action,
                       std::move(action), barrier_closure));
  }
}

void ArcAsh::ConvertTextSelectionAction(
    mojom::TextSelectionActionPtr* converted_action,
    arc::mojom::TextSelectionActionPtr action,
    base::OnceClosure callback,
    const gfx::ImageSkia& image) {
  // Convert actions to crosapi::mojom::TextSelectionActionPtr from
  // arc::mojom::TextSelectionActionPtr and ImageSkia icon.

  // Generate app_id by looking up ArcAppListPrefs.
  std::string app_id = ArcAppListPrefs::Get(profile_)->GetAppIdByPackageName(
      action->activity->package_name);
  *converted_action = mojom::TextSelectionAction::New(
      std::move(app_id), image,
      ConvertArcActivityName(std::move(action->activity)),
      std::move(action->title),
      ConvertArcIntentInfo(std::move(action->action_intent)));

  std::move(callback).Run();
}

void ArcAsh::HandleUrl(const std::string& url,
                       const std::string& package_name) {
  auto* intent_helper_holder = GetIntentHelperHolder();
  if (!intent_helper_holder)
    return;

  auto* instance = ARC_GET_INSTANCE_FOR_METHOD(intent_helper_holder, HandleUrl);
  if (!instance) {
    LOG(WARNING) << "HandleUrl is not supported.";
    return;
  }

  instance->HandleUrl(url, package_name);
}

void ArcAsh::HandleIntent(mojom::IntentInfoPtr intent,
                          mojom::ActivityNamePtr activity) {
  auto* intent_helper_holder = GetIntentHelperHolder();
  if (!intent_helper_holder)
    return;

  auto* instance =
      ARC_GET_INSTANCE_FOR_METHOD(intent_helper_holder, HandleIntent);
  if (!instance) {
    LOG(WARNING) << "HandleIntent is not supported.";
    return;
  }

  arc::mojom::IntentInfoPtr converted_intent = arc::mojom::IntentInfo::New();
  converted_intent->action = intent->action;
  converted_intent->categories = intent->categories;
  converted_intent->data = intent->data;
  converted_intent->type = intent->type;
  converted_intent->ui_bypassed = intent->ui_bypassed;
  converted_intent->extras = intent->extras;
  instance->HandleIntent(std::move(converted_intent),
                         arc::mojom::ActivityName::New(
                             activity->package_name, activity->activity_name));
}

void ArcAsh::AddPreferredPackage(const std::string& package_name) {
  auto* intent_helper_holder = GetIntentHelperHolder();
  if (!intent_helper_holder)
    return;

  auto* instance =
      ARC_GET_INSTANCE_FOR_METHOD(intent_helper_holder, AddPreferredPackage);
  if (!instance) {
    LOG(WARNING) << "AddPreferredPackage is not supported.";
    return;
  }

  instance->AddPreferredPackage(package_name);
}

void ArcAsh::IsInstallable(const std::string& package_name,
                           IsInstallableCallback callback) {
  auto* arc_service_manager = arc::ArcServiceManager::Get();
  if (!arc_service_manager) {
    std::move(callback).Run(
        crosapi::mojom::IsInstallableResult::kArcIsNotRunning);
    return;
  }
  auto* instance = ARC_GET_INSTANCE_FOR_METHOD(
      arc_service_manager->arc_bridge_service()->app(), IsInstallable);
  if (!instance) {
    std::move(callback).Run(crosapi::mojom::IsInstallableResult::kArcIsTooOld);
    return;
  }
  instance->IsInstallable(
      package_name, base::BindOnce(&OnIsInstallable, std::move(callback)));
}

void ArcAsh::OnIconInvalidated(const std::string& package_name) {
  for (auto& observer : observers_)
    observer->OnIconInvalidated(package_name);
}

void ArcAsh::OnProfileWillBeDestroyed(Profile* profile) {
  CHECK_EQ(profile_, profile);
  profile_ = nullptr;
  profile_observation_.Reset();
}

}  // namespace crosapi