| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/apps/app_service/arc_apps.h" |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "ash/public/cpp/app_menu_constants.h" |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/containers/contains.h" |
| #include "base/containers/flat_map.h" |
| #include "base/feature_list.h" |
| #include "base/files/file_path.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/optional.h" |
| #include "base/task/post_task.h" |
| #include "base/task/thread_pool.h" |
| #include "chrome/browser/apps/app_service/app_service_proxy.h" |
| #include "chrome/browser/apps/app_service/app_service_proxy_factory.h" |
| #include "chrome/browser/apps/app_service/arc_apps_factory.h" |
| #include "chrome/browser/apps/app_service/dip_px_util.h" |
| #include "chrome/browser/apps/app_service/file_utils.h" |
| #include "chrome/browser/apps/app_service/menu_util.h" |
| #include "chrome/browser/chromeos/arc/arc_util.h" |
| #include "chrome/browser/chromeos/arc/session/arc_session_manager.h" |
| #include "chrome/browser/chromeos/file_manager/path_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/app_list/arc/arc_app_icon.h" |
| #include "chrome/browser/ui/app_list/arc/arc_app_utils.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/grit/component_extension_resources.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/arc/app_permissions/arc_app_permissions_bridge.h" |
| #include "components/arc/arc_service_manager.h" |
| #include "components/arc/arc_util.h" |
| #include "components/arc/intent_helper/intent_constants.h" |
| #include "components/arc/mojom/app_permissions.mojom.h" |
| #include "components/arc/mojom/file_system.mojom.h" |
| #include "components/arc/session/arc_bridge_service.h" |
| #include "components/full_restore/app_launch_info.h" |
| #include "components/full_restore/full_restore_utils.h" |
| #include "components/services/app_service/public/cpp/intent_filter_util.h" |
| #include "components/services/app_service/public/cpp/intent_util.h" |
| #include "extensions/grit/extensions_browser_resources.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/gfx/image/image_skia_operations.h" |
| |
| // TODO(crbug.com/826982): consider that, per khmel@, "App icon can be |
| // overwritten (setTaskDescription) or by assigning the icon for the app |
| // window. In this case some consumers (Shelf for example) switch to |
| // overwritten icon... IIRC this applies to shelf items and ArcAppWindow icon". |
| |
| namespace { |
| |
| constexpr char kIntentExtraText[] = "android.intent.extra.TEXT"; |
| constexpr char kIntentExtraSubject[] = "android.intent.extra.SUBJECT"; |
| |
| void CompleteWithCompressed(apps::mojom::Publisher::LoadIconCallback callback, |
| std::vector<uint8_t> data) { |
| if (data.empty()) { |
| std::move(callback).Run(apps::mojom::IconValue::New()); |
| return; |
| } |
| apps::mojom::IconValuePtr iv = apps::mojom::IconValue::New(); |
| iv->icon_type = apps::mojom::IconType::kCompressed; |
| iv->compressed = std::move(data); |
| iv->is_placeholder_icon = false; |
| std::move(callback).Run(std::move(iv)); |
| } |
| |
| void OnArcAppIconCompletelyLoaded( |
| apps::mojom::IconType icon_type, |
| int32_t size_hint_in_dip, |
| apps::IconEffects icon_effects, |
| apps::mojom::Publisher::LoadIconCallback callback, |
| ArcAppIcon* icon) { |
| if (!icon) { |
| std::move(callback).Run(apps::mojom::IconValue::New()); |
| return; |
| } |
| |
| apps::mojom::IconValuePtr iv = apps::mojom::IconValue::New(); |
| iv->icon_type = icon_type; |
| iv->is_placeholder_icon = false; |
| |
| switch (icon_type) { |
| case apps::mojom::IconType::kCompressed: |
| if (!base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon)) { |
| auto& compressed_images = icon->compressed_images(); |
| auto iter = |
| compressed_images.find(apps_util::GetPrimaryDisplayUIScaleFactor()); |
| if (iter == compressed_images.end()) { |
| std::move(callback).Run(apps::mojom::IconValue::New()); |
| return; |
| } |
| const std::string& data = iter->second; |
| iv->compressed = std::vector<uint8_t>(data.begin(), data.end()); |
| if (icon_effects != apps::IconEffects::kNone) { |
| // TODO(crbug.com/988321): decompress the image, apply icon effects |
| // then re-compress. |
| } |
| break; |
| } |
| FALLTHROUGH; |
| case apps::mojom::IconType::kUncompressed: |
| FALLTHROUGH; |
| case apps::mojom::IconType::kStandard: { |
| if (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon)) { |
| iv->uncompressed = |
| icon->is_adaptive_icon() |
| ? apps::CompositeImagesAndApplyMask( |
| icon->foreground_image_skia(), |
| icon->background_image_skia()) |
| : apps::ApplyBackgroundAndMask(icon->foreground_image_skia()); |
| } else { |
| iv->uncompressed = icon->image_skia(); |
| } |
| if (icon_effects != apps::IconEffects::kNone) { |
| apps::ApplyIconEffects(icon_effects, size_hint_in_dip, |
| &iv->uncompressed); |
| } |
| break; |
| } |
| case apps::mojom::IconType::kUnknown: |
| NOTREACHED(); |
| break; |
| } |
| |
| if (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon) && |
| icon_type == apps::mojom::IconType::kCompressed) { |
| iv->uncompressed.MakeThreadSafe(); |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE}, |
| base::BindOnce(&apps::EncodeImageToPngBytes, iv->uncompressed, |
| /*rep_icon_scale=*/1.0f), |
| base::BindOnce(&CompleteWithCompressed, std::move(callback))); |
| return; |
| } |
| std::move(callback).Run(std::move(iv)); |
| } |
| |
| void UpdateAppPermissions( |
| const base::flat_map<arc::mojom::AppPermission, |
| arc::mojom::PermissionStatePtr>& new_permissions, |
| std::vector<apps::mojom::PermissionPtr>* permissions) { |
| for (const auto& new_permission : new_permissions) { |
| auto permission = apps::mojom::Permission::New(); |
| permission->permission_id = static_cast<uint32_t>(new_permission.first); |
| permission->value_type = apps::mojom::PermissionValueType::kBool; |
| permission->value = static_cast<uint32_t>(new_permission.second->granted); |
| permission->is_managed = new_permission.second->managed; |
| |
| permissions->push_back(std::move(permission)); |
| } |
| } |
| |
| base::Optional<arc::UserInteractionType> GetUserInterationType( |
| apps::mojom::LaunchSource launch_source) { |
| auto user_interaction_type = arc::UserInteractionType::NOT_USER_INITIATED; |
| switch (launch_source) { |
| // kUnknown is not set anywhere, this case is not valid. |
| case apps::mojom::LaunchSource::kUnknown: |
| return base::nullopt; |
| case apps::mojom::LaunchSource::kFromAppListGrid: |
| user_interaction_type = |
| arc::UserInteractionType::APP_STARTED_FROM_LAUNCHER; |
| break; |
| case apps::mojom::LaunchSource::kFromAppListGridContextMenu: |
| user_interaction_type = |
| arc::UserInteractionType::APP_STARTED_FROM_LAUNCHER_CONTEXT_MENU; |
| break; |
| case apps::mojom::LaunchSource::kFromAppListQuery: |
| user_interaction_type = |
| arc::UserInteractionType::APP_STARTED_FROM_LAUNCHER_SEARCH; |
| break; |
| case apps::mojom::LaunchSource::kFromAppListQueryContextMenu: |
| user_interaction_type = arc::UserInteractionType:: |
| APP_STARTED_FROM_LAUNCHER_SEARCH_CONTEXT_MENU; |
| break; |
| case apps::mojom::LaunchSource::kFromAppListRecommendation: |
| user_interaction_type = |
| arc::UserInteractionType::APP_STARTED_FROM_LAUNCHER_SUGGESTED_APP; |
| break; |
| case apps::mojom::LaunchSource::kFromParentalControls: |
| user_interaction_type = |
| arc::UserInteractionType::APP_STARTED_FROM_SETTINGS; |
| break; |
| case apps::mojom::LaunchSource::kFromShelf: |
| user_interaction_type = arc::UserInteractionType::APP_STARTED_FROM_SHELF; |
| break; |
| case apps::mojom::LaunchSource::kFromFileManager: |
| user_interaction_type = |
| arc::UserInteractionType::APP_STARTED_FROM_FILE_MANAGER; |
| break; |
| case apps::mojom::LaunchSource::kFromLink: |
| user_interaction_type = arc::UserInteractionType::APP_STARTED_FROM_LINK; |
| break; |
| case apps::mojom::LaunchSource::kFromOmnibox: |
| user_interaction_type = |
| arc::UserInteractionType::APP_STARTED_FROM_OMNIBOX; |
| break; |
| case apps::mojom::LaunchSource::kFromSharesheet: |
| user_interaction_type = |
| arc::UserInteractionType::APP_STARTED_FROM_SHARESHEET; |
| break; |
| default: |
| NOTREACHED(); |
| return base::nullopt; |
| } |
| return user_interaction_type; |
| } |
| |
| base::flat_map<std::string, std::string> CreateIntentExtras( |
| const apps::mojom::IntentPtr& intent) { |
| auto extras = base::flat_map<std::string, std::string>(); |
| if (intent->share_text.has_value()) { |
| extras.insert(std::make_pair(kIntentExtraText, intent->share_text.value())); |
| } |
| if (intent->share_title.has_value()) { |
| extras.insert( |
| std::make_pair(kIntentExtraSubject, intent->share_title.value())); |
| } |
| return extras; |
| } |
| |
| const char* GetArcIntentAction(const std::string& action) { |
| if (action == apps_util::kIntentActionView) { |
| return arc::kIntentActionView; |
| } else if (action == apps_util::kIntentActionSend) { |
| return arc::kIntentActionSend; |
| } else if (action == apps_util::kIntentActionSendMultiple) { |
| return arc::kIntentActionSendMultiple; |
| } else { |
| return arc::kIntentActionView; |
| } |
| } |
| |
| arc::mojom::IntentInfoPtr CreateArcIntent(apps::mojom::IntentPtr intent) { |
| arc::mojom::IntentInfoPtr arc_intent; |
| if (!intent->url.has_value() && !intent->share_text.has_value()) { |
| return arc_intent; |
| } |
| arc_intent = arc::mojom::IntentInfo::New(); |
| if (intent->action.has_value()) { |
| arc_intent->action = GetArcIntentAction(intent->action.value()); |
| } else { |
| arc_intent->action = arc::kIntentActionView; |
| } |
| if (intent->url.has_value()) { |
| arc_intent->data = intent->url->spec(); |
| } |
| if (intent->share_text.has_value() || intent->share_title.has_value()) { |
| arc_intent->extras = CreateIntentExtras(intent); |
| } |
| return arc_intent; |
| } |
| |
| apps::mojom::IntentFilterPtr ConvertArcIntentFilter( |
| const arc::IntentFilter& arc_intent_filter) { |
| auto intent_filter = apps::mojom::IntentFilter::New(); |
| |
| if (base::FeatureList::IsEnabled(features::kIntentHandlingSharing)) { |
| std::vector<apps::mojom::ConditionValuePtr> action_condition_values; |
| for (auto& arc_action : arc_intent_filter.actions()) { |
| std::string action; |
| if (arc_action == arc::kIntentActionView) { |
| action = apps_util::kIntentActionView; |
| } else if (arc_action == arc::kIntentActionSend) { |
| action = apps_util::kIntentActionSend; |
| } else if (arc_action == arc::kIntentActionSendMultiple) { |
| action = apps_util::kIntentActionSendMultiple; |
| } else { |
| continue; |
| } |
| action_condition_values.push_back(apps_util::MakeConditionValue( |
| action, apps::mojom::PatternMatchType::kNone)); |
| } |
| if (!action_condition_values.empty()) { |
| auto action_condition = |
| apps_util::MakeCondition(apps::mojom::ConditionType::kAction, |
| std::move(action_condition_values)); |
| intent_filter->conditions.push_back(std::move(action_condition)); |
| } |
| } |
| |
| std::vector<apps::mojom::ConditionValuePtr> scheme_condition_values; |
| for (auto& scheme : arc_intent_filter.schemes()) { |
| scheme_condition_values.push_back(apps_util::MakeConditionValue( |
| scheme, apps::mojom::PatternMatchType::kNone)); |
| } |
| if (!scheme_condition_values.empty()) { |
| auto scheme_condition = |
| apps_util::MakeCondition(apps::mojom::ConditionType::kScheme, |
| std::move(scheme_condition_values)); |
| intent_filter->conditions.push_back(std::move(scheme_condition)); |
| } |
| |
| std::vector<apps::mojom::ConditionValuePtr> host_condition_values; |
| for (auto& authority : arc_intent_filter.authorities()) { |
| host_condition_values.push_back(apps_util::MakeConditionValue( |
| authority.host(), apps::mojom::PatternMatchType::kNone)); |
| } |
| if (!host_condition_values.empty()) { |
| auto host_condition = apps_util::MakeCondition( |
| apps::mojom::ConditionType::kHost, std::move(host_condition_values)); |
| intent_filter->conditions.push_back(std::move(host_condition)); |
| } |
| |
| std::vector<apps::mojom::ConditionValuePtr> path_condition_values; |
| for (auto& path : arc_intent_filter.paths()) { |
| apps::mojom::PatternMatchType match_type; |
| switch (path.match_type()) { |
| case arc::mojom::PatternType::PATTERN_LITERAL: |
| match_type = apps::mojom::PatternMatchType::kLiteral; |
| break; |
| case arc::mojom::PatternType::PATTERN_PREFIX: |
| match_type = apps::mojom::PatternMatchType::kPrefix; |
| break; |
| case arc::mojom::PatternType::PATTERN_SIMPLE_GLOB: |
| match_type = apps::mojom::PatternMatchType::kGlob; |
| break; |
| } |
| path_condition_values.push_back( |
| apps_util::MakeConditionValue(path.pattern(), match_type)); |
| } |
| if (!path_condition_values.empty()) { |
| auto path_condition = apps_util::MakeCondition( |
| apps::mojom::ConditionType::kPattern, std::move(path_condition_values)); |
| intent_filter->conditions.push_back(std::move(path_condition)); |
| } |
| |
| if (base::FeatureList::IsEnabled(features::kIntentHandlingSharing)) { |
| std::vector<apps::mojom::ConditionValuePtr> mime_type_condition_values; |
| for (auto& mime_type : arc_intent_filter.mime_types()) { |
| mime_type_condition_values.push_back(apps_util::MakeConditionValue( |
| mime_type, apps::mojom::PatternMatchType::kMimeType)); |
| } |
| if (!mime_type_condition_values.empty()) { |
| auto mime_type_condition = |
| apps_util::MakeCondition(apps::mojom::ConditionType::kMimeType, |
| std::move(mime_type_condition_values)); |
| intent_filter->conditions.push_back(std::move(mime_type_condition)); |
| } |
| if (!arc_intent_filter.activity_name().empty()) { |
| intent_filter->activity_name = arc_intent_filter.activity_name(); |
| } |
| if (!arc_intent_filter.activity_label().empty()) { |
| intent_filter->activity_label = arc_intent_filter.activity_label(); |
| } |
| } |
| |
| return intent_filter; |
| } |
| |
| arc::IntentFilter CreateArcIntentFilter( |
| const std::string& package_name, |
| const apps::mojom::IntentFilterPtr& intent_filter) { |
| std::vector<std::string> actions; |
| std::vector<std::string> schemes; |
| std::vector<arc::IntentFilter::AuthorityEntry> authorities; |
| std::vector<arc::IntentFilter::PatternMatcher> paths; |
| std::vector<std::string> mime_types; |
| for (auto& condition : intent_filter->conditions) { |
| switch (condition->condition_type) { |
| case apps::mojom::ConditionType::kScheme: |
| for (auto& condition_value : condition->condition_values) { |
| schemes.push_back(condition_value->value); |
| } |
| break; |
| case apps::mojom::ConditionType::kHost: |
| for (auto& condition_value : condition->condition_values) { |
| authorities.push_back(arc::IntentFilter::AuthorityEntry( |
| /*host=*/condition_value->value, /*port=*/0)); |
| } |
| break; |
| case apps::mojom::ConditionType::kPattern: |
| for (auto& condition_value : condition->condition_values) { |
| arc::mojom::PatternType match_type; |
| switch (condition_value->match_type) { |
| case apps::mojom::PatternMatchType::kLiteral: |
| match_type = arc::mojom::PatternType::PATTERN_LITERAL; |
| break; |
| case apps::mojom::PatternMatchType::kPrefix: |
| match_type = arc::mojom::PatternType::PATTERN_PREFIX; |
| break; |
| case apps::mojom::PatternMatchType::kGlob: |
| match_type = arc::mojom::PatternType::PATTERN_SIMPLE_GLOB; |
| break; |
| case apps::mojom::PatternMatchType::kNone: |
| case apps::mojom::PatternMatchType::kMimeType: |
| NOTREACHED(); |
| return arc::IntentFilter(); |
| } |
| paths.push_back(arc::IntentFilter::PatternMatcher( |
| condition_value->value, match_type)); |
| } |
| break; |
| case apps::mojom::ConditionType::kAction: |
| for (auto& condition_value : condition->condition_values) { |
| actions.push_back(GetArcIntentAction(condition_value->value)); |
| } |
| break; |
| case apps::mojom::ConditionType::kMimeType: |
| for (auto& condition_value : condition->condition_values) { |
| mime_types.push_back(condition_value->value); |
| } |
| break; |
| } |
| } |
| // TODO(crbug.com/853604): Add support for other category types. |
| return arc::IntentFilter(package_name, std::move(actions), |
| std::move(authorities), std::move(paths), |
| std::move(schemes), std::move(mime_types)); |
| } |
| |
| // Check if this intent filter only contains HTTP and HTTPS schemes. |
| bool IsHttpOrHttpsIntentFilter( |
| const apps::mojom::IntentFilterPtr& intent_filter) { |
| for (const auto& condition : intent_filter->conditions) { |
| if (condition->condition_type != apps::mojom::ConditionType::kScheme) { |
| continue; |
| } |
| for (const auto& condition_value : condition->condition_values) { |
| if (condition_value->value != url::kHttpScheme && |
| condition_value->value != url::kHttpsScheme) { |
| return false; |
| } |
| } |
| return true; |
| } |
| // If there is no scheme |condition_type| found, return false. |
| return false; |
| } |
| |
| void AddPreferredApp(const std::string& app_id, |
| const apps::mojom::IntentFilterPtr& intent_filter, |
| apps::mojom::IntentPtr intent, |
| arc::ArcServiceManager* arc_service_manager, |
| ArcAppListPrefs* prefs) { |
| arc::mojom::IntentHelperInstance* instance = nullptr; |
| if (arc_service_manager) { |
| instance = ARC_GET_INSTANCE_FOR_METHOD( |
| arc_service_manager->arc_bridge_service()->intent_helper(), |
| AddPreferredApp); |
| } |
| if (!instance) { |
| return; |
| } |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(app_id); |
| |
| // If |app_info| doesn't exist, we are trying to set preferences for a |
| // non-ARC app. Set the preferred app as the ARC intent helper package. |
| const std::string& package_name = |
| app_info ? app_info->package_name |
| : arc::ArcIntentHelperBridge::kArcIntentHelperPackageName; |
| |
| instance->AddPreferredApp(package_name, |
| CreateArcIntentFilter(package_name, intent_filter), |
| CreateArcIntent(std::move(intent))); |
| } |
| |
| void ResetVerifiedLinks( |
| const apps::mojom::IntentFilterPtr& intent_filter, |
| const apps::mojom::ReplacedAppPreferencesPtr& replaced_app_preferences, |
| arc::ArcServiceManager* arc_service_manager, |
| ArcAppListPrefs* prefs) { |
| arc::mojom::IntentHelperInstance* instance = nullptr; |
| if (arc_service_manager) { |
| instance = ARC_GET_INSTANCE_FOR_METHOD( |
| arc_service_manager->arc_bridge_service()->intent_helper(), |
| ResetVerifiedLinks); |
| } |
| if (!instance) { |
| return; |
| } |
| std::vector<std::string> package_names; |
| |
| // Find the apps that needs to reset verified link domain status in ARC. |
| for (auto& entry : replaced_app_preferences->replaced_preference) { |
| auto app_info = prefs->GetApp(entry.first); |
| if (!app_info) { |
| continue; |
| } |
| for (auto& intent_filter : entry.second) { |
| if (IsHttpOrHttpsIntentFilter(intent_filter)) { |
| package_names.push_back(app_info->package_name); |
| break; |
| } |
| } |
| } |
| instance->ResetVerifiedLinks(package_names); |
| } |
| |
| bool ShouldShow(const ArcAppListPrefs::AppInfo& app_info) { |
| return app_info.show_in_launcher; |
| } |
| |
| void RequestDomainVerificationStatusUpdate(ArcAppListPrefs* prefs) { |
| auto* arc_service_manager = arc::ArcServiceManager::Get(); |
| arc::mojom::IntentHelperInstance* instance = nullptr; |
| |
| if (arc_service_manager) { |
| instance = ARC_GET_INSTANCE_FOR_METHOD( |
| arc_service_manager->arc_bridge_service()->intent_helper(), |
| RequestDomainVerificationStatusUpdate); |
| } |
| if (!instance) { |
| return; |
| } |
| instance->RequestDomainVerificationStatusUpdate(); |
| } |
| |
| bool ShouldSkipFilter(const arc::IntentFilter& arc_intent_filter) { |
| return !base::FeatureList::IsEnabled(features::kIntentHandlingSharing) && |
| std::any_of(arc_intent_filter.actions().begin(), |
| arc_intent_filter.actions().end(), |
| [](const std::string& action) { |
| return action == arc::kIntentActionSend || |
| action == arc::kIntentActionSendMultiple; |
| }); |
| } |
| |
| arc::mojom::ActionType GetArcActionType(const std::string& action) { |
| if (action == apps_util::kIntentActionView) { |
| return arc::mojom::ActionType::VIEW; |
| } else if (action == apps_util::kIntentActionSend) { |
| return arc::mojom::ActionType::SEND; |
| } else if (action == apps_util::kIntentActionSendMultiple) { |
| return arc::mojom::ActionType::SEND_MULTIPLE; |
| } else { |
| return arc::mojom::ActionType::VIEW; |
| } |
| } |
| |
| // Constructs an OpenUrlsRequest to be passed to |
| // FileSystemInstance.OpenUrlsWithPermission. |
| arc::mojom::OpenUrlsRequestPtr ConstructOpenUrlsRequest( |
| const apps::mojom::IntentPtr& intent, |
| const arc::mojom::ActivityNamePtr& activity, |
| const std::vector<GURL>& content_urls) { |
| arc::mojom::OpenUrlsRequestPtr request = arc::mojom::OpenUrlsRequest::New(); |
| request->action_type = GetArcActionType(intent->action.value()); |
| request->activity_name = activity.Clone(); |
| for (const auto& content_url : content_urls) { |
| arc::mojom::ContentUrlWithMimeTypePtr url_with_type = |
| arc::mojom::ContentUrlWithMimeType::New(); |
| url_with_type->content_url = content_url; |
| url_with_type->mime_type = intent->mime_type.value(); |
| request->urls.push_back(std::move(url_with_type)); |
| } |
| if (intent->share_text.has_value() || intent->share_title.has_value()) { |
| request->extras = CreateIntentExtras(intent); |
| } |
| return request; |
| } |
| |
| void OnContentUrlResolved(const base::FilePath& file_path, |
| const std::string& app_id, |
| int32_t event_flags, |
| int64_t display_id, |
| apps::mojom::IntentPtr intent, |
| arc::mojom::ActivityNamePtr activity, |
| const std::vector<GURL>& content_urls) { |
| for (const auto& content_url : content_urls) { |
| if (!content_url.is_valid()) { |
| LOG(ERROR) << "Share files failed, file urls are not valid"; |
| return; |
| } |
| } |
| |
| auto* arc_service_manager = arc::ArcServiceManager::Get(); |
| if (!arc_service_manager) { |
| return; |
| } |
| |
| arc::mojom::FileSystemInstance* arc_file_system = ARC_GET_INSTANCE_FOR_METHOD( |
| arc_service_manager->arc_bridge_service()->file_system(), |
| OpenUrlsWithPermission); |
| if (!arc_file_system) { |
| return; |
| } |
| |
| arc_file_system->OpenUrlsWithPermission( |
| ConstructOpenUrlsRequest(intent, activity, content_urls), |
| base::DoNothing()); |
| |
| ::full_restore::SaveAppLaunchInfo( |
| file_path, std::make_unique<full_restore::AppLaunchInfo>( |
| app_id, event_flags, std::move(intent), display_id)); |
| } |
| |
| } // namespace |
| |
| namespace apps { |
| |
| // static |
| ArcApps* ArcApps::Get(Profile* profile) { |
| return ArcAppsFactory::GetForProfile(profile); |
| } |
| |
| // static |
| ArcApps* ArcApps::CreateForTesting(Profile* profile, |
| apps::AppServiceProxy* proxy) { |
| return new ArcApps(profile, proxy); |
| } |
| |
| ArcApps::ArcApps(Profile* profile) : ArcApps(profile, nullptr) {} |
| |
| ArcApps::ArcApps(Profile* profile, apps::AppServiceProxy* proxy) |
| : profile_(profile), |
| arc_icon_once_loader_(profile), |
| settings_app_is_active_(false) { |
| if (!arc::IsArcAllowedForProfile(profile_) || |
| (arc::ArcServiceManager::Get() == nullptr)) { |
| return; |
| } |
| |
| if (!proxy) { |
| proxy = apps::AppServiceProxyFactory::GetForProfile(profile); |
| } |
| mojo::Remote<apps::mojom::AppService>& app_service = proxy->AppService(); |
| if (!app_service.is_bound()) { |
| return; |
| } |
| |
| // Make some observee-observer connections. |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_); |
| if (!prefs) { |
| return; |
| } |
| prefs->AddObserver(this); |
| proxy->SetArcIsRegistered(); |
| |
| auto* intent_helper_bridge = |
| arc::ArcIntentHelperBridge::GetForBrowserContext(profile_); |
| if (intent_helper_bridge) { |
| if (base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon)) { |
| intent_helper_bridge->SetAdaptiveIconDelegate( |
| &arc_activity_adaptive_icon_impl_); |
| } |
| arc_intent_helper_observer_.Add(intent_helper_bridge); |
| } |
| |
| // There is no MessageCenterController for unit tests, so observe when the |
| // MessageCenterController is created in production code. |
| if (ash::ArcNotificationsHostInitializer::Get()) { |
| notification_initializer_observer_.Add( |
| ash::ArcNotificationsHostInitializer::Get()); |
| } |
| |
| auto* instance_registry = &proxy->InstanceRegistry(); |
| if (instance_registry) { |
| instance_registry_observer_.Add(instance_registry); |
| } |
| |
| PublisherBase::Initialize(app_service, apps::mojom::AppType::kArc); |
| } |
| |
| ArcApps::~ArcApps() = default; |
| |
| void ArcApps::Shutdown() { |
| // Disconnect the observee-observer connections that we made during the |
| // constructor. |
| // |
| // This isn't entirely correct. The object returned by |
| // ArcAppListPrefs::Get(some_profile) can vary over the lifetime of that |
| // profile. If it changed, we'll try to disconnect from different |
| // ArcAppListPrefs-related objects than the ones we connected to, at the time |
| // of this object's construction. |
| // |
| // Even so, this is probably harmless, assuming that calling |
| // foo->RemoveObserver(bar) is a no-op (and e.g. does not crash) if bar |
| // wasn't observing foo in the first place, and assuming that the dangling |
| // observee-observer connection on the old foo's are never followed again. |
| // |
| // To fix this properly, we would probably need to add something like an |
| // OnArcAppListPrefsWillBeDestroyed method to ArcAppListPrefs::Observer, and |
| // in this class's implementation of that method, disconnect. Furthermore, |
| // when the new ArcAppListPrefs object is created, we'll have to somehow be |
| // notified so we can re-connect this object as an observer. |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_); |
| if (prefs) { |
| prefs->RemoveObserver(this); |
| } |
| arc_icon_once_loader_.StopObserving(prefs); |
| |
| auto* intent_helper_bridge = |
| arc::ArcIntentHelperBridge::GetForBrowserContext(profile_); |
| if (intent_helper_bridge && |
| base::FeatureList::IsEnabled(features::kAppServiceAdaptiveIcon)) { |
| intent_helper_bridge->SetAdaptiveIconDelegate(nullptr); |
| } |
| |
| arc_intent_helper_observer_.RemoveAll(); |
| } |
| |
| void ArcApps::Connect( |
| mojo::PendingRemote<apps::mojom::Subscriber> subscriber_remote, |
| apps::mojom::ConnectOptionsPtr opts) { |
| std::vector<apps::mojom::AppPtr> apps; |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_); |
| if (prefs) { |
| for (const auto& app_id : prefs->GetAppIds()) { |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = |
| prefs->GetApp(app_id); |
| if (app_info) { |
| apps.push_back(Convert(prefs, app_id, *app_info)); |
| } |
| } |
| } |
| mojo::Remote<apps::mojom::Subscriber> subscriber( |
| std::move(subscriber_remote)); |
| subscriber->OnApps(std::move(apps)); |
| subscribers_.Add(std::move(subscriber)); |
| } |
| |
| void ArcApps::LoadIcon(const std::string& app_id, |
| apps::mojom::IconKeyPtr icon_key, |
| apps::mojom::IconType icon_type, |
| int32_t size_hint_in_dip, |
| bool allow_placeholder_icon, |
| LoadIconCallback callback) { |
| if (!icon_key || icon_type == apps::mojom::IconType::kUnknown) { |
| std::move(callback).Run(apps::mojom::IconValue::New()); |
| return; |
| } |
| IconEffects icon_effects = static_cast<IconEffects>(icon_key->icon_effects); |
| |
| // Treat the Play Store as a special case, loading an icon defined by a |
| // resource instead of asking the Android VM (or the cache of previous |
| // responses from the Android VM). Presumably this is for bootstrapping: |
| // the Play Store icon (the UI for enabling and installing Android apps) |
| // should be showable even before the user has installed their first |
| // Android app and before bringing up an Android VM for the first time. |
| if (app_id == arc::kPlayStoreAppId) { |
| LoadPlayStoreIcon(icon_type, size_hint_in_dip, icon_effects, |
| std::move(callback)); |
| } else { |
| const ArcAppListPrefs* arc_prefs = ArcAppListPrefs::Get(profile_); |
| DCHECK(arc_prefs); |
| |
| // If the app has been removed, immediately terminate the icon request since |
| // it can't possibly succeed. |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = |
| arc_prefs->GetApp(app_id); |
| if (!app_info) { |
| std::move(callback).Run(apps::mojom::IconValue::New()); |
| return; |
| } |
| |
| arc_icon_once_loader_.LoadIcon( |
| app_id, size_hint_in_dip, icon_type, |
| base::BindOnce(&OnArcAppIconCompletelyLoaded, icon_type, |
| size_hint_in_dip, icon_effects, std::move(callback))); |
| } |
| } |
| |
| void ArcApps::Launch(const std::string& app_id, |
| int32_t event_flags, |
| apps::mojom::LaunchSource launch_source, |
| int64_t display_id) { |
| auto user_interaction_type = GetUserInterationType(launch_source); |
| if (!user_interaction_type.has_value()) { |
| return; |
| } |
| |
| arc::LaunchApp(profile_, app_id, event_flags, user_interaction_type.value(), |
| display_id); |
| |
| full_restore::SaveAppLaunchInfo(profile_->GetPath(), |
| std::make_unique<full_restore::AppLaunchInfo>( |
| app_id, event_flags, display_id)); |
| } |
| |
| void ArcApps::LaunchAppWithIntent(const std::string& app_id, |
| int32_t event_flags, |
| apps::mojom::IntentPtr intent, |
| apps::mojom::LaunchSource launch_source, |
| int64_t display_id) { |
| auto user_interaction_type = GetUserInterationType(launch_source); |
| if (!user_interaction_type.has_value()) { |
| return; |
| } |
| |
| UMA_HISTOGRAM_ENUMERATION("Arc.UserInteraction", |
| user_interaction_type.value()); |
| |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_); |
| if (!prefs) { |
| return; |
| } |
| const std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = |
| prefs->GetApp(app_id); |
| if (!app_info) { |
| LOG(ERROR) << "Launch App failed, could not find app with id " << app_id; |
| return; |
| } |
| |
| if (app_info->ready) { |
| arc::mojom::ActivityNamePtr activity = arc::mojom::ActivityName::New(); |
| activity->package_name = app_info->package_name; |
| if (intent->activity_name.has_value() && |
| !intent->activity_name.value().empty()) { |
| activity->activity_name = intent->activity_name.value(); |
| } |
| |
| if (intent->mime_type.has_value() && intent->file_urls.has_value()) { |
| const auto file_urls = intent->file_urls.value(); |
| file_manager::util::ConvertToContentUrls( |
| apps::GetFileSystemURL(profile_, file_urls), |
| base::BindOnce(&OnContentUrlResolved, profile_->GetPath(), app_id, |
| event_flags, display_id, std::move(intent), |
| std::move(activity))); |
| return; |
| } |
| |
| auto* arc_service_manager = arc::ArcServiceManager::Get(); |
| arc::mojom::IntentHelperInstance* instance = nullptr; |
| if (arc_service_manager) { |
| instance = ARC_GET_INSTANCE_FOR_METHOD( |
| arc_service_manager->arc_bridge_service()->intent_helper(), |
| HandleIntent); |
| } |
| if (!instance) { |
| return; |
| } |
| |
| auto intent_for_full_restore = intent.Clone(); |
| auto arc_intent = CreateArcIntent(std::move(intent)); |
| |
| if (!arc_intent) { |
| LOG(ERROR) << "Launch App failed, launch intent is not valid"; |
| return; |
| } |
| |
| instance->HandleIntent(std::move(arc_intent), std::move(activity)); |
| |
| prefs->SetLastLaunchTime(app_id); |
| |
| full_restore::SaveAppLaunchInfo( |
| profile_->GetPath(), |
| std::make_unique<full_restore::AppLaunchInfo>( |
| app_id, event_flags, std::move(intent_for_full_restore), |
| display_id)); |
| return; |
| } |
| |
| if (arc::IsArcPlayStoreEnabledForProfile(profile_)) { |
| // Handle the case when default app tries to re-activate OptIn flow. |
| if (arc::IsArcPlayStoreEnabledPreferenceManagedForProfile(profile_) && |
| !arc::ArcSessionManager::Get()->enable_requested() && |
| prefs->IsDefault(app_id)) { |
| arc::SetArcPlayStoreEnabledForProfile(profile_, true); |
| // PlayStore item has special handling for shelf controllers. In order |
| // to avoid unwanted initial animation for PlayStore item do not create |
| // deferred launch request when PlayStore item enables Google Play |
| // Store. |
| if (app_id == arc::kPlayStoreAppId) { |
| prefs->SetLastLaunchTime(app_id); |
| return; |
| } |
| } |
| } else { |
| if (prefs->IsDefault(app_id)) { |
| // The setting can fail if the preference is managed. However, the |
| // caller is responsible to not call this function in such case. DCHECK |
| // is here to prevent possible mistake. |
| if (!arc::SetArcPlayStoreEnabledForProfile(profile_, true)) { |
| return; |
| } |
| DCHECK(arc::IsArcPlayStoreEnabledForProfile(profile_)); |
| |
| // PlayStore item has special handling for shelf controllers. In order |
| // to avoid unwanted initial animation for PlayStore item do not create |
| // deferred launch request when PlayStore item enables Google Play |
| // Store. |
| if (app_id == arc::kPlayStoreAppId) { |
| prefs->SetLastLaunchTime(app_id); |
| return; |
| } |
| } else { |
| // Only reachable when ARC always starts. |
| DCHECK(arc::ShouldArcAlwaysStart()); |
| } |
| } |
| } |
| |
| void ArcApps::SetPermission(const std::string& app_id, |
| apps::mojom::PermissionPtr permission) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_); |
| if (!prefs) { |
| return; |
| } |
| const std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = |
| prefs->GetApp(app_id); |
| if (!app_info) { |
| LOG(ERROR) << "SetPermission failed, could not find app with id " << app_id; |
| return; |
| } |
| |
| auto* arc_service_manager = arc::ArcServiceManager::Get(); |
| if (!arc_service_manager) { |
| LOG(WARNING) << "SetPermission failed, ArcServiceManager not available."; |
| return; |
| } |
| |
| auto permission_type = |
| static_cast<arc::mojom::AppPermission>(permission->permission_id); |
| if (permission->value) { |
| auto* permissions_instance = ARC_GET_INSTANCE_FOR_METHOD( |
| arc_service_manager->arc_bridge_service()->app_permissions(), |
| GrantPermission); |
| if (permissions_instance) { |
| permissions_instance->GrantPermission(app_info->package_name, |
| permission_type); |
| } |
| } else { |
| auto* permissions_instance = ARC_GET_INSTANCE_FOR_METHOD( |
| arc_service_manager->arc_bridge_service()->app_permissions(), |
| RevokePermission); |
| if (permissions_instance) { |
| permissions_instance->RevokePermission(app_info->package_name, |
| permission_type); |
| } |
| } |
| } |
| |
| void ArcApps::Uninstall(const std::string& app_id, |
| apps::mojom::UninstallSource uninstall_source, |
| bool clear_site_data, |
| bool report_abuse) { |
| arc::UninstallArcApp(app_id, profile_); |
| } |
| |
| void ArcApps::PauseApp(const std::string& app_id) { |
| if (paused_apps_.MaybeAddApp(app_id)) { |
| SetIconEffect(app_id); |
| } |
| |
| constexpr bool kPaused = true; |
| Publish(paused_apps_.GetAppWithPauseStatus(apps::mojom::AppType::kArc, app_id, |
| kPaused), |
| subscribers_); |
| |
| CloseTasks(app_id); |
| } |
| |
| void ArcApps::UnpauseApps(const std::string& app_id) { |
| if (paused_apps_.MaybeRemoveApp(app_id)) { |
| SetIconEffect(app_id); |
| } |
| |
| constexpr bool kPaused = false; |
| Publish(paused_apps_.GetAppWithPauseStatus(apps::mojom::AppType::kArc, app_id, |
| kPaused), |
| subscribers_); |
| } |
| |
| void ArcApps::StopApp(const std::string& app_id) { |
| CloseTasks(app_id); |
| } |
| |
| void ArcApps::GetMenuModel(const std::string& app_id, |
| apps::mojom::MenuType menu_type, |
| int64_t display_id, |
| GetMenuModelCallback callback) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_); |
| if (!prefs) { |
| std::move(callback).Run(apps::mojom::MenuItems::New()); |
| return; |
| } |
| const std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = |
| prefs->GetApp(app_id); |
| if (!app_info) { |
| std::move(callback).Run(apps::mojom::MenuItems::New()); |
| return; |
| } |
| |
| apps::mojom::MenuItemsPtr menu_items = apps::mojom::MenuItems::New(); |
| |
| // Add Open item if the app is not opened and not suspended. |
| if (!base::Contains(app_id_to_task_ids_, app_id) && !app_info->suspended) { |
| AddCommandItem((menu_type == apps::mojom::MenuType::kAppList) |
| ? ash::LAUNCH_NEW |
| : ash::MENU_OPEN_NEW, |
| IDS_APP_CONTEXT_MENU_ACTIVATE_ARC, &menu_items); |
| } |
| |
| if (app_info->shortcut) { |
| AddCommandItem(ash::UNINSTALL, IDS_APP_LIST_REMOVE_SHORTCUT, &menu_items); |
| } else if (app_info->ready && !app_info->sticky) { |
| AddCommandItem(ash::UNINSTALL, IDS_APP_LIST_UNINSTALL_ITEM, &menu_items); |
| } |
| |
| // App Info item. |
| if (app_info->ready && ShouldShow(*app_info)) { |
| AddCommandItem(ash::SHOW_APP_INFO, IDS_APP_CONTEXT_MENU_SHOW_INFO, |
| &menu_items); |
| } |
| |
| if (menu_type == apps::mojom::MenuType::kShelf && |
| base::Contains(app_id_to_task_ids_, app_id)) { |
| AddCommandItem(ash::MENU_CLOSE, IDS_SHELF_CONTEXT_MENU_CLOSE, &menu_items); |
| } |
| |
| BuildMenuForShortcut(app_info->package_name, std::move(menu_items), |
| std::move(callback)); |
| } |
| |
| void ArcApps::ExecuteContextMenuCommand(const std::string& app_id, |
| int command_id, |
| const std::string& shortcut_id, |
| int64_t display_id) { |
| arc::ExecuteArcShortcutCommand(profile_, app_id, shortcut_id, display_id); |
| } |
| |
| void ArcApps::OpenNativeSettings(const std::string& app_id) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_); |
| if (!prefs) { |
| return; |
| } |
| const std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = |
| prefs->GetApp(app_id); |
| if (!app_info) { |
| LOG(ERROR) << "Cannot open native settings for " << app_id |
| << ". App is not found."; |
| return; |
| } |
| arc::ShowPackageInfo(app_info->package_name, |
| arc::mojom::ShowPackageInfoPage::MAIN, |
| display::Screen::GetScreen()->GetPrimaryDisplay().id()); |
| } |
| |
| void ArcApps::OnPreferredAppSet( |
| const std::string& app_id, |
| apps::mojom::IntentFilterPtr intent_filter, |
| apps::mojom::IntentPtr intent, |
| apps::mojom::ReplacedAppPreferencesPtr replaced_app_preferences) { |
| auto* arc_service_manager = arc::ArcServiceManager::Get(); |
| |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_); |
| if (!prefs) { |
| return; |
| } |
| AddPreferredApp(app_id, intent_filter, std::move(intent), arc_service_manager, |
| prefs); |
| ResetVerifiedLinks(intent_filter, replaced_app_preferences, |
| arc_service_manager, prefs); |
| } |
| |
| void ArcApps::OnAppRegistered(const std::string& app_id, |
| const ArcAppListPrefs::AppInfo& app_info) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_); |
| if (prefs) { |
| Publish(Convert(prefs, app_id, app_info), subscribers_); |
| } |
| } |
| |
| void ArcApps::OnAppStatesChanged(const std::string& app_id, |
| const ArcAppListPrefs::AppInfo& app_info) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_); |
| if (!prefs) { |
| return; |
| } |
| |
| Publish(Convert(prefs, app_id, app_info), subscribers_); |
| } |
| |
| void ArcApps::OnAppRemoved(const std::string& app_id) { |
| app_notifications_.RemoveNotificationsForApp(app_id); |
| paused_apps_.MaybeRemoveApp(app_id); |
| |
| if (base::Contains(app_id_to_task_ids_, app_id)) { |
| for (int task_id : app_id_to_task_ids_[app_id]) { |
| task_id_to_app_id_.erase(task_id); |
| } |
| app_id_to_task_ids_.erase(app_id); |
| } |
| |
| apps::mojom::AppPtr app = apps::mojom::App::New(); |
| app->app_type = apps::mojom::AppType::kArc; |
| app->app_id = app_id; |
| app->readiness = apps::mojom::Readiness::kUninstalledByUser; |
| Publish(std::move(app), subscribers_); |
| |
| mojo::Remote<apps::mojom::AppService>& app_service = |
| apps::AppServiceProxyFactory::GetForProfile(profile_)->AppService(); |
| if (!app_service.is_bound()) { |
| return; |
| } |
| app_service->RemovePreferredApp(apps::mojom::AppType::kArc, app_id); |
| } |
| |
| void ArcApps::OnAppIconUpdated(const std::string& app_id, |
| const ArcAppIconDescriptor& descriptor) { |
| SetIconEffect(app_id); |
| } |
| |
| void ArcApps::OnAppNameUpdated(const std::string& app_id, |
| const std::string& name) { |
| apps::mojom::AppPtr app = apps::mojom::App::New(); |
| app->app_type = apps::mojom::AppType::kArc; |
| app->app_id = app_id; |
| app->name = name; |
| Publish(std::move(app), subscribers_); |
| } |
| |
| void ArcApps::OnAppLastLaunchTimeUpdated(const std::string& app_id) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_); |
| if (!prefs) { |
| return; |
| } |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(app_id); |
| if (!app_info) { |
| return; |
| } |
| apps::mojom::AppPtr app = apps::mojom::App::New(); |
| app->app_type = apps::mojom::AppType::kArc; |
| app->app_id = app_id; |
| app->last_launch_time = app_info->last_launch_time; |
| Publish(std::move(app), subscribers_); |
| } |
| |
| void ArcApps::OnPackageInstalled( |
| const arc::mojom::ArcPackageInfo& package_info) { |
| ConvertAndPublishPackageApps(package_info); |
| } |
| |
| void ArcApps::OnPackageModified( |
| const arc::mojom::ArcPackageInfo& package_info) { |
| static constexpr bool update_icon = false; |
| ConvertAndPublishPackageApps(package_info, update_icon); |
| } |
| |
| void ArcApps::OnPackageListInitialRefreshed() { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_); |
| if (!prefs) { |
| return; |
| } |
| // This method is called when ARC++ finishes booting. Do not update the icon; |
| // it should be impossible for the icon to have changed since ARC++ has not |
| // been running. |
| static constexpr bool update_icon = false; |
| for (const auto& app_id : prefs->GetAppIds()) { |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(app_id); |
| if (app_info) { |
| Publish(Convert(prefs, app_id, *app_info, update_icon), subscribers_); |
| } |
| } |
| } |
| |
| void ArcApps::OnTaskCreated(int task_id, |
| const std::string& package_name, |
| const std::string& activity, |
| const std::string& intent) { |
| const std::string app_id = ArcAppListPrefs::GetAppId(package_name, activity); |
| app_id_to_task_ids_[app_id].insert(task_id); |
| task_id_to_app_id_[task_id] = app_id; |
| } |
| |
| void ArcApps::OnTaskDestroyed(int task_id) { |
| auto it = task_id_to_app_id_.find(task_id); |
| if (it == task_id_to_app_id_.end()) { |
| return; |
| } |
| |
| const std::string app_id = it->second; |
| task_id_to_app_id_.erase(it); |
| DCHECK(base::Contains(app_id_to_task_ids_, app_id)); |
| app_id_to_task_ids_[app_id].erase(task_id); |
| if (app_id_to_task_ids_[app_id].empty()) { |
| app_id_to_task_ids_.erase(app_id); |
| } |
| } |
| |
| void ArcApps::OnIntentFiltersUpdated( |
| const base::Optional<std::string>& package_name) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_); |
| if (!prefs) { |
| return; |
| } |
| |
| auto GetAppInfoAndPublish = [prefs, this](std::string app_id) { |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(app_id); |
| if (app_info) { |
| Publish(Convert(prefs, app_id, *app_info, false /* update_icon */), |
| subscribers_); |
| } |
| }; |
| |
| // If there is no specific package_name, update all apps, otherwise update |
| // apps for the package. |
| |
| // Note: Cannot combine the two for-loops because the return type of |
| // GetAppIds() is std::vector<std::string> and the return type of |
| // GetAppsForPackage() is std::unordered_set<std::string>. |
| if (package_name == base::nullopt) { |
| for (const auto& app_id : prefs->GetAppIds()) { |
| GetAppInfoAndPublish(app_id); |
| } |
| } else { |
| for (const auto& app_id : prefs->GetAppsForPackage(package_name.value())) { |
| GetAppInfoAndPublish(app_id); |
| } |
| } |
| } |
| |
| void ArcApps::OnPreferredAppsChanged() { |
| if (!base::FeatureList::IsEnabled(features::kAppServiceIntentHandling)) { |
| return; |
| } |
| mojo::Remote<apps::mojom::AppService>& app_service = |
| apps::AppServiceProxyFactory::GetForProfile(profile_)->AppService(); |
| if (!app_service.is_bound()) { |
| return; |
| } |
| |
| auto* intent_helper_bridge = |
| arc::ArcIntentHelperBridge::GetForBrowserContext(profile_); |
| if (!intent_helper_bridge) { |
| return; |
| } |
| |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_); |
| if (!prefs) { |
| return; |
| } |
| |
| const std::vector<arc::IntentFilter>& added_preferred_apps = |
| intent_helper_bridge->GetAddedPreferredApps(); |
| |
| for (auto& added_preferred_app : added_preferred_apps) { |
| if (ShouldSkipFilter(added_preferred_app)) { |
| continue; |
| } |
| |
| constexpr bool kFromPublisher = true; |
| // TODO(crbug.com/853604): Currently only handles one App ID per package. |
| // If need to handle multiple activities per package, will need to |
| // update ARC to send through the corresponding activity and ensure this |
| // activity matches with the main_activity that stored in app_service. |
| // Will add an activity field in the arc::mojom::intent_filter. |
| // Also need to make sure this still work with the Chrome set preference |
| // because the intent filter uplifted for each package doesn't contain |
| // activity info. |
| std::string app_id = |
| prefs->GetAppIdByPackageName(added_preferred_app.package_name()); |
| |
| if (app_id.empty()) { |
| LOG(ERROR) << "Cannot get app id for package " |
| << added_preferred_app.package_name() |
| << " to add preferred app."; |
| continue; |
| } |
| app_service->AddPreferredApp(apps::mojom::AppType::kArc, app_id, |
| ConvertArcIntentFilter(added_preferred_app), |
| /*intent=*/nullptr, kFromPublisher); |
| } |
| |
| const std::vector<arc::IntentFilter>& deleted_preferred_apps = |
| intent_helper_bridge->GetDeletedPreferredApps(); |
| |
| for (auto& deleted_preferred_app : deleted_preferred_apps) { |
| if (ShouldSkipFilter(deleted_preferred_app)) { |
| continue; |
| } |
| // TODO(crbug.com/853604): Currently only handles one App ID per package. |
| // If need to handle multiple activities per package, will need to |
| // update ARC to send through the corresponding activity and ensure this |
| // activity matches with the main_activity that stored in app_service. |
| // Will add an activity field in the arc::mojom::intent_filter. |
| // Also need to make sure this still work with the Chrome set preference |
| // because the intent filter uplifted for each package doesn't contain |
| // activity info. |
| std::string app_id = |
| prefs->GetAppIdByPackageName(deleted_preferred_app.package_name()); |
| if (app_id.empty()) { |
| LOG(ERROR) << "Cannot get app id by package " |
| << deleted_preferred_app.package_name() |
| << " to delete preferred app."; |
| continue; |
| } |
| app_service->RemovePreferredAppForFilter( |
| apps::mojom::AppType::kArc, app_id, |
| ConvertArcIntentFilter(deleted_preferred_app)); |
| } |
| } |
| |
| void ArcApps::OnSetArcNotificationsInstance( |
| ash::ArcNotificationManagerBase* arc_notification_manager) { |
| DCHECK(arc_notification_manager); |
| notification_observer_.Add(arc_notification_manager); |
| } |
| |
| void ArcApps::OnArcNotificationInitializerDestroyed( |
| ash::ArcNotificationsHostInitializer* initializer) { |
| notification_initializer_observer_.Remove(initializer); |
| } |
| |
| void ArcApps::OnNotificationUpdated(const std::string& notification_id, |
| const std::string& app_id) { |
| if (app_id.empty()) { |
| return; |
| } |
| |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_); |
| if (!prefs) { |
| return; |
| } |
| |
| const std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = |
| prefs->GetApp(app_id); |
| if (!app_info) { |
| return; |
| } |
| |
| app_notifications_.AddNotification(app_id, notification_id); |
| Publish(app_notifications_.GetAppWithHasBadgeStatus( |
| apps::mojom::AppType::kArc, app_id), |
| subscribers_); |
| } |
| |
| void ArcApps::OnNotificationRemoved(const std::string& notification_id) { |
| const auto app_ids = |
| app_notifications_.GetAppIdsForNotification(notification_id); |
| if (app_ids.empty()) { |
| return; |
| } |
| |
| app_notifications_.RemoveNotification(notification_id); |
| |
| for (const auto& app_id : app_ids) { |
| Publish(app_notifications_.GetAppWithHasBadgeStatus( |
| apps::mojom::AppType::kArc, app_id), |
| subscribers_); |
| } |
| } |
| |
| void ArcApps::OnArcNotificationManagerDestroyed( |
| ash::ArcNotificationManagerBase* notification_manager) { |
| notification_observer_.Remove(notification_manager); |
| } |
| |
| void ArcApps::OnInstanceUpdate(const apps::InstanceUpdate& update) { |
| if (!update.StateChanged()) { |
| return; |
| } |
| if (update.AppId() != arc::kSettingsAppId) { |
| return; |
| } |
| if (update.State() & apps::InstanceState::kActive) { |
| settings_app_is_active_ = true; |
| } else if (settings_app_is_active_) { |
| settings_app_is_active_ = false; |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_); |
| if (!prefs) { |
| return; |
| } |
| RequestDomainVerificationStatusUpdate(prefs); |
| } |
| } |
| |
| void ArcApps::OnInstanceRegistryWillBeDestroyed( |
| apps::InstanceRegistry* instance_registry) { |
| instance_registry_observer_.Remove(instance_registry); |
| } |
| |
| void ArcApps::LoadPlayStoreIcon(apps::mojom::IconType icon_type, |
| int32_t size_hint_in_dip, |
| IconEffects icon_effects, |
| LoadIconCallback callback) { |
| // Use overloaded Chrome icon for Play Store that is adapted to Chrome style. |
| constexpr bool quantize_to_supported_scale_factor = true; |
| int size_hint_in_px = apps_util::ConvertDipToPx( |
| size_hint_in_dip, quantize_to_supported_scale_factor); |
| int resource_id = (size_hint_in_px <= 32) ? IDR_ARC_SUPPORT_ICON_32 |
| : IDR_ARC_SUPPORT_ICON_192; |
| constexpr bool is_placeholder_icon = false; |
| LoadIconFromResource(icon_type, size_hint_in_dip, resource_id, |
| is_placeholder_icon, icon_effects, std::move(callback)); |
| } |
| |
| apps::mojom::InstallSource GetInstallSource( |
| const ArcAppListPrefs* prefs, |
| const ArcAppListPrefs::AppInfo* app_info) { |
| // Sticky represents apps that cannot be uninstalled and are installed by the |
| // system. |
| if (app_info->sticky) { |
| return apps::mojom::InstallSource::kSystem; |
| } |
| |
| if (prefs->IsDefault(app_info->package_name)) { |
| return apps::mojom::InstallSource::kDefault; |
| } |
| |
| if (prefs->IsOem(app_info->package_name)) { |
| return apps::mojom::InstallSource::kOem; |
| } |
| |
| if (prefs->IsControlledByPolicy(app_info->package_name)) { |
| return apps::mojom::InstallSource::kPolicy; |
| } |
| |
| return apps::mojom::InstallSource::kUser; |
| } |
| |
| apps::mojom::AppPtr ArcApps::Convert(ArcAppListPrefs* prefs, |
| const std::string& app_id, |
| const ArcAppListPrefs::AppInfo& app_info, |
| bool update_icon) { |
| apps::mojom::AppPtr app = PublisherBase::MakeApp( |
| apps::mojom::AppType::kArc, app_id, |
| app_info.suspended ? apps::mojom::Readiness::kDisabledByPolicy |
| : apps::mojom::Readiness::kReady, |
| app_info.name, GetInstallSource(prefs, &app_info)); |
| |
| app->publisher_id = app_info.package_name; |
| |
| auto paused = paused_apps_.IsPaused(app_id) |
| ? apps::mojom::OptionalBool::kTrue |
| : apps::mojom::OptionalBool::kFalse; |
| |
| if (update_icon) { |
| app->icon_key = |
| icon_key_factory_.MakeIconKey(GetIconEffects(app_id, app_info)); |
| } |
| |
| app->last_launch_time = app_info.last_launch_time; |
| app->install_time = app_info.install_time; |
| |
| auto show = ShouldShow(app_info) ? apps::mojom::OptionalBool::kTrue |
| : apps::mojom::OptionalBool::kFalse; |
| app->show_in_launcher = show; |
| app->show_in_shelf = show; |
| app->show_in_search = show; |
| app->show_in_management = show; |
| |
| app->has_badge = app_notifications_.HasNotification(app_id) |
| ? apps::mojom::OptionalBool::kTrue |
| : apps::mojom::OptionalBool::kFalse; |
| app->paused = paused; |
| |
| std::unique_ptr<ArcAppListPrefs::PackageInfo> package = |
| prefs->GetPackage(app_info.package_name); |
| if (package) { |
| UpdateAppPermissions(package->permissions, &app->permissions); |
| } |
| |
| auto* intent_helper_bridge = |
| arc::ArcIntentHelperBridge::GetForBrowserContext(profile_); |
| if (intent_helper_bridge && |
| app_info.package_name != |
| arc::ArcIntentHelperBridge::kArcIntentHelperPackageName) { |
| UpdateAppIntentFilters(app_info.package_name, intent_helper_bridge, |
| &app->intent_filters); |
| } |
| |
| return app; |
| } |
| |
| void ArcApps::ConvertAndPublishPackageApps( |
| const arc::mojom::ArcPackageInfo& package_info, |
| bool update_icon) { |
| if (!package_info.permissions.has_value()) { |
| return; |
| } |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_); |
| if (prefs) { |
| for (const auto& app_id : |
| prefs->GetAppsForPackage(package_info.package_name)) { |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = |
| prefs->GetApp(app_id); |
| if (app_info) { |
| Publish(Convert(prefs, app_id, *app_info, update_icon), subscribers_); |
| } |
| } |
| } |
| } |
| |
| IconEffects ArcApps::GetIconEffects(const std::string& app_id, |
| const ArcAppListPrefs::AppInfo& app_info) { |
| IconEffects icon_effects = IconEffects::kNone; |
| if (app_info.suspended) { |
| icon_effects = |
| static_cast<IconEffects>(icon_effects | IconEffects::kBlocked); |
| } |
| if (paused_apps_.IsPaused(app_id)) { |
| icon_effects = |
| static_cast<IconEffects>(icon_effects | IconEffects::kPaused); |
| } |
| return icon_effects; |
| } |
| |
| void ArcApps::SetIconEffect(const std::string& app_id) { |
| ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_); |
| if (!prefs) { |
| return; |
| } |
| std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(app_id); |
| if (!app_info) { |
| return; |
| } |
| |
| apps::mojom::AppPtr app = apps::mojom::App::New(); |
| app->app_type = apps::mojom::AppType::kArc; |
| app->app_id = app_id; |
| app->icon_key = |
| icon_key_factory_.MakeIconKey(GetIconEffects(app_id, *app_info)); |
| Publish(std::move(app), subscribers_); |
| } |
| |
| void ArcApps::CloseTasks(const std::string& app_id) { |
| if (!base::Contains(app_id_to_task_ids_, app_id)) { |
| return; |
| } |
| |
| for (int task_id : app_id_to_task_ids_[app_id]) { |
| arc::CloseTask(task_id); |
| task_id_to_app_id_.erase(task_id); |
| } |
| app_id_to_task_ids_.erase(app_id); |
| } |
| |
| void ArcApps::UpdateAppIntentFilters( |
| std::string package_name, |
| arc::ArcIntentHelperBridge* intent_helper_bridge, |
| std::vector<apps::mojom::IntentFilterPtr>* intent_filters) { |
| const std::vector<arc::IntentFilter>& arc_intent_filters = |
| intent_helper_bridge->GetIntentFilterForPackage(package_name); |
| for (auto& arc_intent_filter : arc_intent_filters) { |
| if (ShouldSkipFilter(arc_intent_filter)) { |
| continue; |
| } |
| intent_filters->push_back(ConvertArcIntentFilter(arc_intent_filter)); |
| } |
| } |
| |
| void ArcApps::BuildMenuForShortcut(const std::string& package_name, |
| apps::mojom::MenuItemsPtr menu_items, |
| GetMenuModelCallback callback) { |
| // The previous request is cancelled, and start a new request if the callback |
| // of the previous request is not called. |
| arc_app_shortcuts_request_ = |
| std::make_unique<arc::ArcAppShortcutsRequest>(base::BindOnce( |
| &ArcApps::OnGetAppShortcutItems, weak_ptr_factory_.GetWeakPtr(), |
| base::TimeTicks::Now(), std::move(menu_items), std::move(callback))); |
| arc_app_shortcuts_request_->StartForPackage(package_name); |
| } |
| |
| void ArcApps::OnGetAppShortcutItems( |
| const base::TimeTicks start_time, |
| apps::mojom::MenuItemsPtr menu_items, |
| GetMenuModelCallback callback, |
| std::unique_ptr<arc::ArcAppShortcutItems> app_shortcut_items) { |
| if (!app_shortcut_items || app_shortcut_items->empty()) { |
| // No need log time for empty requests. |
| std::move(callback).Run(std::move(menu_items)); |
| arc_app_shortcuts_request_.reset(); |
| return; |
| } |
| |
| arc::ArcAppShortcutItems& items = *app_shortcut_items; |
| // Sort the shortcuts based on two rules: (1) Static (declared in manifest) |
| // shortcuts and then dynamic shortcuts; (2) Within each shortcut type |
| // (static and dynamic), shortcuts are sorted in order of increasing rank. |
| std::sort(items.begin(), items.end(), |
| [](const arc::ArcAppShortcutItem& item1, |
| const arc::ArcAppShortcutItem& item2) { |
| return std::tie(item1.type, item1.rank) < |
| std::tie(item2.type, item2.rank); |
| }); |
| |
| AddSeparator(ui::DOUBLE_SEPARATOR, &menu_items); |
| int command_id = ash::LAUNCH_APP_SHORTCUT_FIRST; |
| for (const auto& item : items) { |
| if (command_id != ash::LAUNCH_APP_SHORTCUT_FIRST) { |
| AddSeparator(ui::PADDED_SEPARATOR, &menu_items); |
| } |
| AddArcCommandItem(command_id++, item.shortcut_id, item.short_label, |
| item.icon, &menu_items); |
| } |
| std::move(callback).Run(std::move(menu_items)); |
| arc_app_shortcuts_request_.reset(); |
| |
| UMA_HISTOGRAM_TIMES("Arc.AppShortcuts.BuildMenuTime", |
| base::TimeTicks::Now() - start_time); |
| } |
| |
| } // namespace apps |