| // 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/ui/app_list/internal_app/internal_app_metadata.h" |
| |
| #include "ash/public/cpp/app_list/internal_app_id_constants.h" |
| #include "ash/public/cpp/resources/grit/ash_public_unscaled_resources.h" |
| #include "base/logging.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/no_destructor.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/string_util.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/chromeos/arc/arc_util.h" |
| #include "chrome/browser/chromeos/profiles/profile_helper.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/sync/profile_sync_service_factory.h" |
| #include "chrome/browser/ui/app_list/app_list_client_impl.h" |
| #include "chrome/browser/ui/app_list/arc/arc_app_utils.h" |
| #include "chrome/browser/ui/ash/ksv/keyboard_shortcut_viewer_util.h" |
| #include "chrome/browser/ui/chrome_pages.h" |
| #include "chrome/browser/ui/extensions/app_launch_params.h" |
| #include "chrome/browser/ui/extensions/application_launch.h" |
| #include "chrome/browser/ui/webui/chromeos/login/discover/discover_window_manager.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/grit/chrome_unscaled_resources.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "chromeos/chromeos_switches.h" |
| #include "components/arc/arc_features_parser.h" |
| #include "components/arc/metrics/arc_metrics_constants.h" |
| #include "components/arc/property/arc_property_bridge.h" |
| #include "components/crx_file/id_util.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/sessions/core/serialized_navigation_entry.h" |
| #include "components/sync/driver/sync_service.h" |
| #include "components/sync/protocol/sync_enums.pb.h" |
| #include "components/sync_sessions/open_tabs_ui_delegate.h" |
| #include "components/sync_sessions/synced_session.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/extension_urls.h" |
| #include "net/base/url_util.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/page_transition_types.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/image/image_skia_operations.h" |
| #include "url/gurl.h" |
| |
| namespace app_list { |
| |
| namespace { |
| constexpr char kChromeCameraAppId[] = "hfhhnacclhffhdffklopdkcgdhifgngh"; |
| |
| // Generated as ArcAppListPrefs::GetAppIdByPackageName( |
| // "com.google.android.GoogleCameraArc"). |
| constexpr char kAndroidCameraAppId[] = "goamfaniemdfcajgcmmflhchgkmbngka"; |
| // Generated as ArcAppListPrefs::GetAppIdByPackageName( |
| // "com.android.camera2"). |
| constexpr char kAndroidLegacyCameraAppId[] = "obfofkigjfamlldmipdegnjlcpincibc"; |
| // Generated as ArcAppListPrefs::GetAppIdByPackageName( |
| // "com.android.googlecameramigration"). |
| constexpr char kAndroidCameraMigrationAppId[] = |
| "ngmkobaiicipbagcngcmilfkhejlnfci"; |
| |
| const std::vector<InternalApp>& GetInternalAppListImpl(bool get_all, |
| const Profile* profile) { |
| DCHECK(get_all || profile); |
| static const base::NoDestructor<std::vector<InternalApp>> |
| internal_app_list_static( |
| {{kInternalAppIdKeyboardShortcutViewer, |
| IDS_INTERNAL_APP_KEYBOARD_SHORTCUT_VIEWER, |
| IDR_SHORTCUT_VIEWER_LOGO_192, |
| /*recommendable=*/false, |
| /*searchable=*/true, |
| /*show_in_launcher=*/false, |
| InternalAppName::kKeyboardShortcutViewer, |
| IDS_LAUNCHER_SEARCHABLE_KEYBOARD_SHORTCUT_VIEWER}, |
| |
| {kInternalAppIdSettings, IDS_INTERNAL_APP_SETTINGS, |
| IDR_SETTINGS_LOGO_192, |
| /*recommendable=*/true, |
| /*searchable=*/true, |
| /*show_in_launcher=*/true, InternalAppName::kSettings, |
| /*searchable_string_resource_id=*/0}, |
| |
| {kInternalAppIdContinueReading, IDS_INTERNAL_APP_CONTINUOUS_READING, |
| IDR_PRODUCT_LOGO_256, |
| /*recommendable=*/true, |
| /*searchable=*/false, |
| /*show_in_launcher=*/false, InternalAppName::kContinueReading, |
| /*searchable_string_resource_id=*/0}}); |
| |
| static base::NoDestructor<std::vector<InternalApp>> internal_app_list; |
| internal_app_list->clear(); |
| internal_app_list->insert(internal_app_list->begin(), |
| internal_app_list_static->begin(), |
| internal_app_list_static->end()); |
| |
| const bool add_camera_app = get_all || !profile->IsGuestSession(); |
| if (add_camera_app) { |
| internal_app_list->push_back( |
| {kInternalAppIdCamera, IDS_INTERNAL_APP_CAMERA, IDR_CAMERA_LOGO_192, |
| /*recommendable=*/true, |
| /*searchable=*/true, |
| /*show_in_launcher=*/true, InternalAppName::kCamera, |
| /*searchable_string_resource_id=*/0}); |
| } |
| |
| const bool add_discover_app = |
| get_all || !chromeos::ProfileHelper::IsEphemeralUserProfile(profile); |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| chromeos::switches::kEnableDiscoverApp) && |
| add_discover_app) { |
| internal_app_list->push_back( |
| {kInternalAppIdDiscover, IDS_INTERNAL_APP_DISCOVER, |
| IDR_DISCOVER_APP_192, |
| /*recommendable=*/false, |
| /*searchable=*/true, |
| /*show_in_launcher=*/true, InternalAppName::kDiscover, |
| /*searchable_string_resource_id=*/IDS_INTERNAL_APP_DISCOVER}); |
| } |
| return *internal_app_list; |
| } |
| |
| } // namespace |
| |
| const std::vector<InternalApp>& GetInternalAppList(const Profile* profile) { |
| return GetInternalAppListImpl(false, profile); |
| } |
| |
| const InternalApp* FindInternalApp(const std::string& app_id) { |
| for (const auto& app : GetInternalAppListImpl(true, nullptr)) { |
| if (app_id == app.app_id) |
| return &app; |
| } |
| return nullptr; |
| } |
| |
| bool IsInternalApp(const std::string& app_id) { |
| return !!FindInternalApp(app_id); |
| } |
| |
| base::string16 GetInternalAppNameById(const std::string& app_id) { |
| const auto* app = FindInternalApp(app_id); |
| return app ? l10n_util::GetStringUTF16(app->name_string_resource_id) |
| : base::string16(); |
| } |
| |
| int GetIconResourceIdByAppId(const std::string& app_id) { |
| const auto* app = FindInternalApp(app_id); |
| return app ? app->icon_resource_id : 0; |
| } |
| |
| void OpenChromeCameraApp(Profile* profile, |
| int event_flags, |
| const extensions::Extension* extension) { |
| AppListClientImpl* controller = AppListClientImpl::GetInstance(); |
| AppLaunchParams params = CreateAppLaunchParamsWithEventFlags( |
| profile, extension, event_flags, extensions::SOURCE_APP_LAUNCHER, |
| controller->GetAppListDisplayId()); |
| params.launch_id = ash::ShelfID(extension->id()).launch_id; |
| OpenApplication(params); |
| VLOG(1) << "Launched CCA."; |
| } |
| |
| void ShowWebStore(Profile* profile, |
| int event_flags, |
| const std::string chrome_app_id) { |
| AppListClientImpl* controller = AppListClientImpl::GetInstance(); |
| const GURL store_url = net::AppendQueryParameter( |
| GURL(extension_urls::GetWebstoreItemDetailURLPrefix() + chrome_app_id), |
| extension_urls::kWebstoreSourceField, |
| extension_urls::kLaunchSourceAppListSearch); |
| controller->OpenURL(profile, store_url, ui::PAGE_TRANSITION_LINK, |
| ui::DispositionFromEventFlags(event_flags)); |
| VLOG(1) << "Launched CWS."; |
| } |
| |
| void OnGetMigrationProperty(Profile* profile, |
| int event_flags, |
| const base::Optional<std::string>& result) { |
| const char* app_id = kAndroidCameraAppId; |
| if (!result.has_value() || result.value() != "true") { |
| VLOG(1) << "GCA migration is not finished. Launch migration app."; |
| app_id = kAndroidCameraMigrationAppId; |
| } |
| |
| AppListClientImpl* controller = AppListClientImpl::GetInstance(); |
| if (arc::LaunchApp(profile, app_id, event_flags, |
| arc::UserInteractionType::APP_STARTED_FROM_LAUNCHER, |
| controller->GetAppListDisplayId())) { |
| VLOG(1) << "Launched " |
| << (app_id == kAndroidCameraAppId ? " GCA." : "GCA migration."); |
| return; |
| } |
| if (arc::LaunchApp(profile, kAndroidLegacyCameraAppId, event_flags, |
| arc::UserInteractionType::APP_STARTED_FROM_LAUNCHER, |
| controller->GetAppListDisplayId())) { |
| VLOG(1) << "Launched legacy GCA."; |
| return; |
| } |
| |
| LOG(ERROR) << "Failed to launch any camera apps. Fallback to CCA."; |
| const std::string chrome_app_id(kChromeCameraAppId); |
| const extensions::ExtensionRegistry* registry = |
| extensions::ExtensionRegistry::Get(profile); |
| const extensions::Extension* extension = |
| registry->GetInstalledExtension(chrome_app_id); |
| if (extension) |
| OpenChromeCameraApp(profile, event_flags, extension); |
| else |
| ShowWebStore(profile, event_flags, chrome_app_id); |
| } |
| |
| bool IsArcPOrAbove(const base::Optional<arc::ArcFeatures>& read_result) { |
| if (read_result == base::nullopt) |
| return false; |
| |
| const std::string version_str = |
| read_result.value().build_props.at("ro.build.version.sdk"); |
| int version = 0; |
| VLOG(1) << "ARC version is " << version_str; |
| return base::StringToInt(version_str, &version) && version >= 28; |
| } |
| |
| void OnArcFeaturesRead(Profile* profile, |
| int event_flags, |
| base::Optional<arc::ArcFeatures> read_result) { |
| bool arc_p_or_above = IsArcPOrAbove(read_result); |
| const std::string chrome_app_id(kChromeCameraAppId); |
| const extensions::ExtensionRegistry* registry = |
| extensions::ExtensionRegistry::Get(profile); |
| const extensions::Extension* extension = |
| registry->GetInstalledExtension(chrome_app_id); |
| |
| bool arc_enabled = arc::IsArcPlayStoreEnabledForProfile(profile); |
| bool chrome_camera_migrated = |
| profile->GetPrefs()->GetBoolean(prefs::kCameraMediaConsolidated); |
| |
| VLOG(1) << "Launching camera app. arc_enabled = " << arc_enabled |
| << " arc_p_or_above = " << arc_p_or_above |
| << " chrome_camera_migrated = " << chrome_camera_migrated |
| << " cca_exist = " << (extension != nullptr); |
| |
| if (arc_enabled && arc_p_or_above && (!extension || chrome_camera_migrated)) { |
| // Open Google camera app or GCA migration app according to GCA |
| // migration system property. |
| arc::ArcPropertyBridge* property = |
| arc::ArcPropertyBridge::GetForBrowserContext(profile); |
| property->GetGcaMigrationProperty( |
| base::BindOnce(&OnGetMigrationProperty, profile, event_flags)); |
| } else if (extension) { |
| OpenChromeCameraApp(profile, event_flags, extension); |
| } else { |
| ShowWebStore(profile, event_flags, chrome_app_id); |
| } |
| } |
| |
| void OpenInternalApp(const std::string& app_id, |
| Profile* profile, |
| int event_flags) { |
| if (app_id == kInternalAppIdKeyboardShortcutViewer) { |
| keyboard_shortcut_viewer_util::ToggleKeyboardShortcutViewer(); |
| } else if (app_id == kInternalAppIdSettings) { |
| chrome::ShowSettingsSubPageForProfile(profile, std::string()); |
| } else if (app_id == kInternalAppIdCamera) { |
| arc::ArcFeaturesParser::GetArcFeatures( |
| base::BindOnce(&OnArcFeaturesRead, profile, event_flags)); |
| } else if (app_id == kInternalAppIdDiscover) { |
| #if defined(OS_CHROMEOS) |
| base::RecordAction(base::UserMetricsAction("ShowDiscover")); |
| chromeos::DiscoverWindowManager::GetInstance() |
| ->ShowChromeDiscoverPageForProfile(profile); |
| #endif |
| } |
| } |
| |
| gfx::ImageSkia GetIconForResourceId(int resource_id, int resource_size_in_dip) { |
| if (resource_id == 0) |
| return gfx::ImageSkia(); |
| |
| gfx::ImageSkia* source = |
| ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(resource_id); |
| return gfx::ImageSkiaOperations::CreateResizedImage( |
| *source, skia::ImageOperations::RESIZE_BEST, |
| gfx::Size(resource_size_in_dip, resource_size_in_dip)); |
| } |
| |
| bool HasRecommendableForeignTab(Profile* profile, |
| base::string16* title, |
| GURL* url) { |
| syncer::SyncService* sync_service = |
| ProfileSyncServiceFactory::GetSyncServiceForBrowserContext(profile); |
| std::vector<const sync_sessions::SyncedSession*> foreign_sessions; |
| sync_sessions::OpenTabsUIDelegate* delegate = |
| sync_service->GetOpenTabsUIDelegate(); |
| if (delegate != nullptr) |
| delegate->GetAllForeignSessions(&foreign_sessions); |
| |
| constexpr int kMaxForeignTabAgeInMinutes = 120; |
| base::Time latest_timestamp; |
| bool has_recommendation = false; |
| for (const sync_sessions::SyncedSession* session : foreign_sessions) { |
| if (latest_timestamp > session->modified_time) |
| continue; |
| |
| auto device_type = session->device_type; |
| if (device_type != sync_pb::SyncEnums::TYPE_PHONE && |
| device_type != sync_pb::SyncEnums::TYPE_TABLET) { |
| continue; |
| } |
| |
| for (const auto& key_value : session->windows) { |
| for (const std::unique_ptr<sessions::SessionTab>& tab : |
| key_value.second->wrapped_window.tabs) { |
| if (tab->navigations.empty()) |
| continue; |
| |
| const sessions::SerializedNavigationEntry& navigation = |
| tab->navigations.back(); |
| const GURL& virtual_url = navigation.virtual_url(); |
| |
| // Only show pages with http or https. |
| if (!virtual_url.SchemeIsHTTPOrHTTPS()) |
| continue; |
| |
| // Only show pages recently opened. |
| const base::TimeDelta tab_age = base::Time::Now() - tab->timestamp; |
| if (tab_age > base::TimeDelta::FromMinutes(kMaxForeignTabAgeInMinutes)) |
| continue; |
| |
| if (latest_timestamp < tab->timestamp) { |
| has_recommendation = true; |
| latest_timestamp = tab->timestamp; |
| if (title) |
| *title = navigation.title(); |
| |
| if (url) |
| *url = virtual_url; |
| } |
| } |
| } |
| } |
| return has_recommendation; |
| } |
| |
| InternalAppName GetInternalAppNameByAppId( |
| const std::string& app_id) { |
| const auto* app = FindInternalApp(app_id); |
| DCHECK(app); |
| return app->internal_app_name; |
| } |
| |
| size_t GetNumberOfInternalAppsShowInLauncherForTest(std::string* apps_name, |
| const Profile* profile) { |
| size_t num_of_internal_apps_show_in_launcher = 0u; |
| std::vector<std::string> internal_apps_name; |
| for (const auto& app : GetInternalAppList(profile)) { |
| if (app.show_in_launcher) { |
| ++num_of_internal_apps_show_in_launcher; |
| if (apps_name) { |
| internal_apps_name.emplace_back( |
| l10n_util::GetStringUTF8(app.name_string_resource_id)); |
| } |
| } |
| } |
| if (apps_name) |
| *apps_name = base::JoinString(internal_apps_name, ","); |
| return num_of_internal_apps_show_in_launcher; |
| } |
| |
| } // namespace app_list |