| // 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. |
| |
| #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) { |
| if (profile_) { |
| LOG(WARNING) << "profile_ is already initialized. Ignoring SetProfile."; |
| return; |
| } |
| |
| profile_ = profile; |
| auto* bridge = arc::ArcIntentHelperBridge::GetForBrowserContext(profile_); |
| if (bridge) |
| bridge->AddObserver(this); |
| } |
| |
| 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() { |
| // This method should not be called if profie_ is not set. |
| DCHECK(profile_); |
| |
| // 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. |
| auto* bridge = arc::ArcIntentHelperBridge::GetForBrowserContext(profile_); |
| 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); |
| } |
| |
| } // namespace crosapi |