// Copyright 2016 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/app_list/arc/arc_app_utils.h"
#include <map>
#include <memory>
#include <string>
#include <tuple>
#include <utility>
#include "ash/components/arc/app/arc_app_launch_notifier.h"
#include "ash/components/arc/arc_features.h"
#include "ash/components/arc/arc_prefs.h"
#include "ash/components/arc/arc_util.h"
#include "ash/components/arc/metrics/arc_metrics_constants.h"
#include "ash/components/arc/metrics/arc_metrics_service.h"
#include "ash/components/arc/mojom/intent_helper.mojom.h"
#include "ash/components/arc/session/arc_bridge_service.h"
#include "ash/components/arc/session/arc_service_manager.h"
#include "base/check.h"
#include "base/check_is_test.h"
#include "base/json/json_writer.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/observer_list.h"
#include "base/scoped_multi_source_observation.h"
#include "base/strings/string_number_conversions.h"
#include "base/values.h"
#include "chrome/browser/apps/app_service/intent_util.h"
#include "chrome/browser/ash/app_list/app_list_client_impl.h"
#include "chrome/browser/ash/app_list/arc/intent.h"
#include "chrome/browser/ash/app_list/search/ranking/launch_data.h"
#include "chrome/browser/ash/app_list/search/search_controller.h"
#include "chrome/browser/ash/app_list/search/types.h"
#include "chrome/browser/ash/arc/arc_migration_guide_notification.h"
#include "chrome/browser/ash/arc/arc_util.h"
#include "chrome/browser/ash/arc/boot_phase_monitor/arc_boot_phase_monitor_bridge.h"
#include "chrome/browser/ash/arc/notification/arc_management_transition_notification.h"
#include "chrome/browser/ash/arc/session/arc_session_manager.h"
#include "chrome/browser/ash/arc/vmm/arc_vmm_manager.h"
#include "chrome/browser/ash/arc/window_predictor/window_predictor.h"
#include "chrome/browser/ash/arc/window_predictor/window_predictor_utils.h"
#include "chrome/browser/ash/login/login_pref_names.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_observer.h"
#include "chrome/browser/ui/ash/shelf/arc_app_shelf_id.h"
#include "chrome/browser/ui/ash/shelf/arc_shelf_spinner_item_controller.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h"
#include "chrome/browser/ui/ash/shelf/shelf_spinner_controller.h"
#include "components/app_restore/app_restore_utils.h"
#include "components/app_restore/features.h"
#include "components/arc/common/intent_helper/arc_intent_helper_package.h"
#include "components/language/core/browser/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/services/app_service/public/cpp/intent_util.h"
#include "content/public/browser/browser_context.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/events/event_constants.h"
#include "ui/gfx/geometry/rect.h"
// Helper macro which returns the AppInstance.
#define GET_APP_INSTANCE(method_name) \
(arc::ArcServiceManager::Get() \
? ARC_GET_INSTANCE_FOR_METHOD( \
arc::ArcServiceManager::Get()->arc_bridge_service()->app(), \
method_name) \
: nullptr)
// Helper function which returns the IntentHelperInstance.
#define GET_INTENT_HELPER_INSTANCE(method_name) \
(arc::ArcServiceManager::Get() \
? ARC_GET_INSTANCE_FOR_METHOD(arc::ArcServiceManager::Get() \
->arc_bridge_service() \
->intent_helper(), \
method_name) \
: nullptr)
namespace arc {
namespace {
// TODO(djacobo): Evaluate to build these strings by using
// ArcIntentHelperBridge::AppendStringToIntentHelperPackageName.
// Intent helper strings.
constexpr char kIntentHelperClassName[] =
"org.chromium.arc.intent_helper.SettingsReceiver";
constexpr char kSetInTouchModeIntent[] =
"org.chromium.arc.intent_helper.SET_IN_TOUCH_MODE";
constexpr char kAndroidClockAppId[] = "ddmmnabaeomoacfpfjgghfpocfolhjlg";
constexpr char kAndroidFilesAppId[] = "gmiohhmfhgfclpeacmdfancbipocempm";
constexpr char const* kAppIdsHiddenInLauncher[] = {
kAndroidClockAppId, kSettingsAppId, kAndroidFilesAppId,
kAndroidContactsAppId, kPlayGamesAppId, kPackageInstallerAppId};
// Returns true if |event_flags| came from a mouse or touch event.
bool IsMouseOrTouchEventFromFlags(int event_flags) {
return (event_flags & (ui::EF_LEFT_MOUSE_BUTTON | ui::EF_MIDDLE_MOUSE_BUTTON |
ui::EF_RIGHT_MOUSE_BUTTON | ui::EF_BACK_MOUSE_BUTTON |
ui::EF_FORWARD_MOUSE_BUTTON | ui::EF_FROM_TOUCH)) != 0;
}
bool Launch(Profile* profile,
const std::string& app_id,
apps::IntentPtr intent,
int event_flags,
arc::mojom::WindowInfoPtr window_info) {
ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile);
CHECK(prefs);
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(app_id);
if (!app_info) {
VLOG(2) << "Cannot launch unavailable app: " << app_id << ".";
return false;
}
if (!app_info->ready) {
VLOG(2) << "Cannot launch not-ready app: " << app_id << ".";
return false;
}
if (!app_info->launchable) {
VLOG(2) << "Cannot launch non-launchable app: " << app_id << ".";
return false;
}
if (app_info->suspended) {
VLOG(2) << "Cannot launch suspended app: " << app_id << ".";
return false;
}
if (IsMouseOrTouchEventFromFlags(event_flags))
SetTouchMode(IsMouseOrTouchEventFromFlags(event_flags));
// Unthrottle the ARC instance before launching an ARC app. This is done
// to minimize lag on an app launch.
auto* notifier = ArcAppLaunchNotifier::GetForBrowserContext(profile);
if (notifier) {
// ArcAppLaunchNotifier may not exist in test environment.
notifier->NotifyArcAppLaunchRequest(app_info->package_name);
} else {
CHECK_IS_TEST();
}
if (app_info->shortcut || intent) {
const std::string intent_uri =
intent ? apps_util::CreateLaunchIntent(app_info->package_name, intent)
: app_info->intent_uri;
if (intent_uri.empty()) {
// If |intent| can't be converted to a string, call the HandleIntent
// interface.
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();
}
auto arc_intent =
apps_util::ConvertAppServiceToArcIntent(std::move(intent));
if (!arc_intent) {
LOG(ERROR) << "Launch App failed, launch intent is not valid";
return false;
}
arc::mojom::IntentHelperInstance* instance =
GET_INTENT_HELPER_INSTANCE(HandleIntentWithWindowInfo);
if (instance) {
instance->HandleIntentWithWindowInfo(
std::move(arc_intent), std::move(activity), std::move(window_info));
} else {
return false;
}
} else {
// If |intent| can be converted to a string, call the Launch interface.
if (auto* app_instance = GET_APP_INSTANCE(LaunchIntentWithWindowInfo)) {
app_instance->LaunchIntentWithWindowInfo(intent_uri,
std::move(window_info));
} else {
return false;
}
}
} else {
if (auto* app_instance = GET_APP_INSTANCE(LaunchAppWithWindowInfo)) {
app_instance->LaunchAppWithWindowInfo(
app_info->package_name, app_info->activity, std::move(window_info));
} else {
return false;
}
}
prefs->SetLastLaunchTime(app_id);
return true;
}
// Returns primary display id if |display_id| is invalid.
int64_t GetValidDisplayId(int64_t display_id) {
if (display_id != display::kInvalidDisplayId)
return display_id;
if (auto* screen = display::Screen::GetScreen())
return screen->GetPrimaryDisplay().id();
return display::kInvalidDisplayId;
}
// Converts an app_id and a shortcut_id, eg. manifest_new_note_shortcut, into a
// full URL for an Arc app shortcut, of the form:
// appshortcutsearch://[app_id]/[shortcut_id].
std::string ConstructArcAppShortcutUrl(const std::string& app_id,
const std::string& shortcut_id) {
return "appshortcutsearch://" + app_id + "/" + shortcut_id;
}
bool IsInstantResponseOpenEnabled() {
return base::FeatureList::IsEnabled(arc::kInstantResponseWindowOpen);
}
bool IsArcVmAndSwappedOut(content::BrowserContext* context) {
return IsArcVmEnabled() &&
base::FeatureList::IsEnabled(arc::kVmmSwapoutGhostWindow) &&
ArcVmmManager::GetForBrowserContext(context)->IsSwapped();
}
} // namespace
bool ShouldShowInLauncher(const std::string& app_id) {
for (auto* const id : kAppIdsHiddenInLauncher) {
if (id == app_id)
return false;
}
return true;
}
arc::mojom::WindowInfoPtr MakeWindowInfo(int64_t display_id) {
arc::mojom::WindowInfoPtr window_info = arc::mojom::WindowInfo::New();
window_info->display_id = display_id;
return window_info;
}
bool LaunchApp(content::BrowserContext* context,
const std::string& app_id,
int event_flags,
arc::UserInteractionType user_action) {
return LaunchAppWithIntent(context, app_id, nullptr /* launch_intent */,
event_flags, user_action,
MakeWindowInfo(display::kInvalidDisplayId));
}
bool LaunchApp(content::BrowserContext* context,
const std::string& app_id,
int event_flags,
arc::UserInteractionType user_action,
arc::mojom::WindowInfoPtr window_info) {
return LaunchAppWithIntent(context, app_id, nullptr /* launch_intent */,
event_flags, user_action, std::move(window_info));
}
bool LaunchAppWithIntent(content::BrowserContext* context,
const std::string& app_id,
apps::IntentPtr launch_intent,
int event_flags,
arc::UserInteractionType user_action,
arc::mojom::WindowInfoPtr window_info) {
if (user_action != UserInteractionType::NOT_USER_INITIATED)
arc::ArcMetricsService::RecordArcUserInteraction(context, user_action);
Profile* const profile = Profile::FromBrowserContext(context);
// Even when ARC is not allowed for the profile, ARC apps may still show up
// as a placeholder to show the guide notification for proper configuration.
// Handle such a case here and shows the desired notification.
if (IsArcBlockedDueToIncompatibleFileSystem(profile)) {
VLOG(1) << "Attempt to launch " << app_id
<< " while ARC++ is blocked due to incompatible file system.";
arc::ShowArcMigrationGuideNotification(profile);
return false;
}
// In case management transition is in progress ARC++ is not available.
const ArcManagementTransition management_transition =
GetManagementTransition(profile);
if (management_transition != ArcManagementTransition::NO_TRANSITION) {
VLOG(1) << "Attempt to launch " << app_id << " while management transition "
<< management_transition << " is in progress.";
arc::ShowManagementTransitionNotification(profile);
return false;
}
// Update display id.
if (window_info)
window_info->display_id = GetValidDisplayId(window_info->display_id);
// Activate ARC in case still not active.
ArcSessionManager::Get()->AllowActivation(
ArcSessionManager::AllowActivationReason::kUserLaunchAction);
ArcAppListPrefs* const prefs = ArcAppListPrefs::Get(context);
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(app_id);
apps::IntentPtr launch_intent_to_send = std::move(launch_intent);
if (!app_info) {
LOG(WARNING) << "Ignore invalid app launch quest, id = " << app_id;
return false;
}
// Some apps need fixup when ARC version upgrade e.g. from ARC P to ARC R.
// Before fixup finishes, the |app_info->ready| is true but not launchable.
if (app_info->need_fixup || !app_info->ready) {
if (!IsArcPlayStoreEnabledForProfile(profile)) {
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 (!SetArcPlayStoreEnabledForProfile(profile, true))
return false;
DCHECK(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 == kPlayStoreAppId) {
prefs->SetLastLaunchTime(app_id);
return true;
}
} else {
// Only reachable when ARC always starts.
DCHECK(arc::ShouldArcAlwaysStart());
}
} else {
// Handle the case when default app tries to re-activate OptIn flow.
if (IsArcPlayStoreEnabledPreferenceManagedForProfile(profile) &&
!ArcSessionManager::Get()->enable_requested() &&
prefs->IsDefault(app_id)) {
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 == kPlayStoreAppId) {
prefs->SetLastLaunchTime(app_id);
return true;
}
}
}
// TODO(sstan): Triage ghost window for different launch source.
// App launched by user rather than full restore.
if (window_info &&
window_info->window_id <=
app_restore::kArcSessionIdOffsetForRestoredLaunching) {
arc::ArcBootPhaseMonitorBridge::RecordFirstAppLaunchDelayUMA(context);
}
if (app_info->need_fixup) {
// TODO(sstan): Use different UI after UX design finalized.
if (WindowPredictor::GetInstance()->LaunchArcAppWithGhostWindow(
profile, app_id, *app_info, launch_intent_to_send, event_flags,
GhostWindowType::kFixup, WindowPredictorUseCase::kArcNotReady,
window_info)) {
prefs->SetLastLaunchTime(app_id);
return true;
}
// Block launch request if failed to launch ghost window.
return false;
} else if (full_restore::features::IsArcWindowPredictorEnabled() &&
arc::GetArcAndroidSdkVersionAsInt() >= arc::kArcVersionR) {
if (WindowPredictor::GetInstance()->LaunchArcAppWithGhostWindow(
profile, app_id, *app_info, launch_intent_to_send, event_flags,
GhostWindowType::kAppLaunch, WindowPredictorUseCase::kArcNotReady,
window_info)) {
prefs->SetLastLaunchTime(app_id);
return true;
}
VLOG(2) << "Failed to launch ghost window, fallback to use shelf spinner";
}
ChromeShelfController* chrome_controller =
ChromeShelfController::instance();
// chrome_controller may be null in tests.
if (chrome_controller) {
chrome_controller->GetShelfSpinnerController()->AddSpinnerToShelf(
app_id, std::make_unique<ArcShelfSpinnerItemController>(
app_id, std::move(launch_intent_to_send), event_flags,
user_action, std::move(window_info)));
// On some boards, ARC is booted with a restricted set of resources by
// default to avoid slowing down Chrome's user session restoration.
// However, the restriction should be lifted once the user explicitly
// tries to launch an ARC app.
auto* notifier = ArcAppLaunchNotifier::GetForBrowserContext(profile);
if (notifier) {
// ArcAppLaunchNotifier may not exist in test environment.
notifier->NotifyArcAppLaunchRequest(app_info->package_name);
} else {
CHECK_IS_TEST();
}
}
prefs->SetLastLaunchTime(app_id);
return true;
} else if (IsArcVmAndSwappedOut(context) &&
!WindowPredictor::GetInstance()->IsAppPendingLaunch(profile,
app_id)) {
// Assume this condition branch will never be triggered in ARCVM launch (ARC
// booting) stage. It should be trigger after ARCVM idle for a while.
if (WindowPredictor::GetInstance()->LaunchArcAppWithGhostWindow(
profile, app_id, *app_info, launch_intent_to_send, event_flags,
GhostWindowType::kAppLaunch, WindowPredictorUseCase::kArcVmmSwapped,
window_info)) {
return true;
}
VLOG(2) << "Failed to launch ghost window for swapped state, fallback to "
"launch directly.";
} else if (app_id == kPlayStoreAppId) {
// Record launch request time in order to track Play Store default launch
// performance.
if (!launch_intent_to_send) {
launch_intent_to_send =
std::make_unique<apps::Intent>(apps_util::kIntentActionMain);
launch_intent_to_send->categories.push_back(kCategoryLauncher);
launch_intent_to_send->activity_name = kPlayStoreActivity;
}
launch_intent_to_send->extras[kRequestStartTimeParamKey] =
base::NumberToString(
(base::TimeTicks::Now() - base::TimeTicks()).InMilliseconds());
} else if (IsInstantResponseOpenEnabled() &&
!WindowPredictor::GetInstance()->IsAppPendingLaunch(profile,
app_id)) {
// For some devices, launch ghost window and app at the same time.
if (WindowPredictor::GetInstance()->LaunchArcAppWithGhostWindow(
profile, app_id, *app_info, launch_intent_to_send, event_flags,
GhostWindowType::kAppLaunch,
WindowPredictorUseCase::kInstanceResponse, window_info)) {
return true;
}
VLOG(2) << "Failed to launch ghost window, fallback to launch directly.";
}
arc::ArcBootPhaseMonitorBridge::RecordFirstAppLaunchDelayUMA(context);
return Launch(profile, app_id, std::move(launch_intent_to_send), event_flags,
std::move(window_info));
}
bool LaunchAppShortcutItem(content::BrowserContext* context,
const std::string& app_id,
const std::string& shortcut_id,
int64_t display_id) {
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
ArcAppListPrefs::Get(context)->GetApp(app_id);
if (!app_info) {
LOG(ERROR) << "App " << app_id << " is not available.";
return false;
}
mojom::AppInstance* app_instance =
ArcServiceManager::Get()
? ARC_GET_INSTANCE_FOR_METHOD(
ArcServiceManager::Get()->arc_bridge_service()->app(),
LaunchAppShortcutItem)
: nullptr;
if (!app_instance) {
LOG(ERROR) << "Cannot find a mojo instance, ARC is unreachable or mojom"
<< " version mismatch.";
return false;
}
app_instance->LaunchAppShortcutItem(app_info->package_name, shortcut_id,
GetValidDisplayId(display_id));
return true;
}
void UpdateWindowInfo(arc::mojom::WindowInfoPtr window_info) {
arc::mojom::AppInstance* app_instance = GET_APP_INSTANCE(UpdateWindowInfo);
if (!app_instance) {
LOG(ERROR) << "Cannot find a mojo instance, ARC is unreachable or mojom"
<< " version mismatch.";
return;
}
app_instance->UpdateWindowInfo(std::move(window_info));
}
void SetTaskActive(int task_id) {
arc::mojom::AppInstance* app_instance = GET_APP_INSTANCE(SetTaskActive);
if (!app_instance)
return;
app_instance->SetTaskActive(task_id);
}
void CloseTask(int task_id) {
arc::mojom::AppInstance* app_instance = GET_APP_INSTANCE(CloseTask);
if (!app_instance)
return;
app_instance->CloseTask(task_id);
}
bool SetTouchMode(bool enable) {
arc::mojom::IntentHelperInstance* intent_helper_instance =
GET_INTENT_HELPER_INSTANCE(SendBroadcast);
if (!intent_helper_instance)
return false;
base::Value::Dict extras;
extras.Set("inTouchMode", enable);
std::string extras_string;
base::JSONWriter::Write(base::Value(std::move(extras)), &extras_string);
intent_helper_instance->SendBroadcast(kSetInTouchModeIntent,
kArcIntentHelperPackageName,
kIntentHelperClassName, extras_string);
return true;
}
std::vector<std::string> GetSelectedPackagesFromPrefs(
content::BrowserContext* context) {
std::vector<std::string> packages;
const Profile* const profile = Profile::FromBrowserContext(context);
const PrefService* prefs = profile->GetPrefs();
const base::Value::List& selected_package_prefs =
prefs->GetList(arc::prefs::kArcFastAppReinstallPackages);
for (const base::Value& item : selected_package_prefs) {
std::string item_str = item.is_string() ? item.GetString() : std::string();
packages.push_back(std::move(item_str));
}
return packages;
}
void StartFastAppReinstallFlow(const std::vector<std::string>& package_names) {
arc::mojom::AppInstance* app_instance =
GET_APP_INSTANCE(StartFastAppReinstallFlow);
if (!app_instance) {
LOG(ERROR) << "Failed to start Fast App Reinstall flow because app "
"instance is not connected.";
return;
}
app_instance->StartFastAppReinstallFlow(package_names);
}
void UninstallPackage(const std::string& package_name) {
VLOG(2) << "Uninstalling " << package_name;
arc::mojom::AppInstance* app_instance = GET_APP_INSTANCE(UninstallPackage);
if (!app_instance)
return;
app_instance->UninstallPackage(package_name);
}
void UninstallArcApp(const std::string& app_id, Profile* profile) {
ArcAppListPrefs* arc_prefs = ArcAppListPrefs::Get(profile);
DCHECK(arc_prefs);
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
arc_prefs->GetApp(app_id);
if (!app_info) {
VLOG(2) << "Package being uninstalled does not exist: " << app_id << ".";
return;
}
// For shortcut we just remove the shortcut instead of the package.
if (app_info->shortcut)
arc_prefs->RemoveApp(app_id);
else
UninstallPackage(app_info->package_name);
}
void RemoveCachedIcon(const std::string& icon_resource_id) {
VLOG(2) << "Removing icon " << icon_resource_id;
arc::mojom::AppInstance* app_instance = GET_APP_INSTANCE(RemoveCachedIcon);
if (!app_instance)
return;
app_instance->RemoveCachedIcon(icon_resource_id);
}
bool ShowPackageInfo(const std::string& package_name,
mojom::ShowPackageInfoPage page,
int64_t display_id) {
VLOG(2) << "Showing package info for " << package_name;
if (auto* app_instance = GET_APP_INSTANCE(ShowPackageInfoOnPage)) {
app_instance->ShowPackageInfoOnPage(package_name, page, display_id);
return true;
}
if (auto* app_instance = GET_APP_INSTANCE(ShowPackageInfoOnPageDeprecated)) {
app_instance->ShowPackageInfoOnPageDeprecated(package_name, page,
gfx::Rect());
return true;
}
if (auto* app_instance = GET_APP_INSTANCE(ShowPackageInfoDeprecated)) {
app_instance->ShowPackageInfoDeprecated(package_name, gfx::Rect());
return true;
}
return false;
}
bool IsArcItem(content::BrowserContext* context, const std::string& id) {
DCHECK(context);
// Some unit tests use empty ids, some app ids are not valid ARC app ids.
const ArcAppShelfId arc_app_shelf_id = ArcAppShelfId::FromString(id);
if (!arc_app_shelf_id.valid())
return false;
const ArcAppListPrefs* const arc_prefs = ArcAppListPrefs::Get(context);
if (!arc_prefs)
return false;
return arc_prefs->IsRegistered(arc_app_shelf_id.app_id());
}
void GetLocaleAndPreferredLanguages(const Profile* profile,
std::string* out_locale,
std::string* out_preferred_languages) {
const PrefService::Preference* locale_pref =
profile->GetPrefs()->FindPreference(
::language::prefs::kApplicationLocale);
DCHECK(locale_pref);
const std::string& locale = locale_pref->GetValue()->GetString();
*out_locale =
locale.empty() ? g_browser_process->GetApplicationLocale() : locale;
// |preferredLanguages| consists of comma separated locale strings. It may be
// empty or contain empty items, but those are ignored on ARC. If an item
// has no country code, it is derived in ARC. In such a case, it may
// conflict with another item in the list, then these will be dedupped (the
// first one is taken) in ARC.
*out_preferred_languages =
profile->GetPrefs()->GetString(::language::prefs::kPreferredLanguages);
}
void GetAndroidId(
base::OnceCallback<void(bool ok, int64_t android_id)> callback) {
auto* app_instance = GET_APP_INSTANCE(GetAndroidId);
if (!app_instance) {
std::move(callback).Run(false, 0);
return;
}
app_instance->GetAndroidId(base::BindOnce(std::move(callback), true));
}
std::string AppIdToArcPackageName(const std::string& app_id, Profile* profile) {
ArcAppListPrefs* arc_prefs = ArcAppListPrefs::Get(profile);
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
arc_prefs->GetApp(app_id);
if (!app_info) {
DLOG(ERROR) << "Couldn't retrieve ARC package name for AppID: " << app_id;
return std::string();
}
return app_info->package_name;
}
std::string ArcPackageNameToAppId(const std::string& package_name,
Profile* profile) {
DCHECK(profile);
ArcAppListPrefs* arc_prefs = ArcAppListPrefs::Get(profile);
return arc_prefs ? arc_prefs->GetAppIdByPackageName(package_name)
: std::string();
}
const std::string GetAppFromAppOrGroupId(content::BrowserContext* context,
const std::string& app_or_group_id) {
const arc::ArcAppShelfId app_shelf_id =
arc::ArcAppShelfId::FromString(app_or_group_id);
if (!app_shelf_id.has_shelf_group_id())
return app_shelf_id.app_id();
const ArcAppListPrefs* const prefs = ArcAppListPrefs::Get(context);
DCHECK(prefs);
// Try to find a shortcut with requested shelf group id.
const std::vector<std::string> app_ids = prefs->GetAppIds();
for (const auto& app_id : app_ids) {
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(app_id);
DCHECK(app_info);
if (!app_info || !app_info->shortcut)
continue;
const arc::ArcAppShelfId shortcut_shelf_id =
arc::ArcAppShelfId::FromIntentAndAppId(app_info->intent_uri, app_id);
if (shortcut_shelf_id.has_shelf_group_id() &&
shortcut_shelf_id.shelf_group_id() == app_shelf_id.shelf_group_id()) {
return app_id;
}
}
// Shortcut with requested shelf group id was not found, use app id as
// fallback.
return app_shelf_id.app_id();
}
void ExecuteArcShortcutCommand(content::BrowserContext* context,
const std::string& id,
const std::string& shortcut_id,
int64_t display_id) {
const arc::ArcAppShelfId arc_shelf_id = arc::ArcAppShelfId::FromString(id);
DCHECK(arc_shelf_id.valid());
arc::LaunchAppShortcutItem(context, arc_shelf_id.app_id(), shortcut_id,
display_id);
// Send a training signal to the search controller.
AppListClientImpl* app_list_client_impl = AppListClientImpl::GetInstance();
if (!app_list_client_impl)
return;
app_list::LaunchData launch_data;
// TODO(crbug.com/40177716): This should set launch_data.launched_from.
launch_data.id =
ConstructArcAppShortcutUrl(arc_shelf_id.app_id(), shortcut_id),
launch_data.result_type = ash::AppListSearchResultType::kArcAppShortcut;
launch_data.category = app_list::Category::kAppShortcuts;
app_list_client_impl->search_controller()->Train(std::move(launch_data));
}
void RecordPlayStoreLaunchWithinAWeek(PrefService* prefs, bool launched) {
if (!prefs->GetBoolean(arc::prefs::kArcPlayStoreLaunchMetricCanBeRecorded))
return;
auto time_oobe_finished = prefs->GetTime(ash::prefs::kOobeOnboardingTime);
if (time_oobe_finished.is_null())
return;
bool within_a_week = base::Time::Now() - time_oobe_finished < base::Days(7);
if (within_a_week && !launched)
return;
base::UmaHistogramBoolean("Arc.PlayStoreLaunchWithinAWeek", within_a_week);
prefs->ClearPref(arc::prefs::kArcPlayStoreLaunchMetricCanBeRecorded);
}
} // namespace arc