blob: cc55bebc78d6e11295b0c46ccb6d5db7ccf070ed [file] [log] [blame]
// Copyright 2012 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/ui/ash/shelf/chrome_shelf_prefs.h"
#include <stddef.h>
#include <algorithm>
#include <map>
#include <memory>
#include <ostream>
#include <utility>
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/shelf_types.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/checked_iterators.h"
#include "base/containers/contains.h"
#include "base/containers/extend.h"
#include "base/containers/span.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_piece_forward.h"
#include "base/strings/string_util.h"
#include "base/values.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/extension_apps_utils.h"
#include "chrome/browser/apps/app_service/policy_util.h"
#include "chrome/browser/ash/app_list/app_list_syncable_service.h"
#include "chrome/browser/ash/app_list/app_list_syncable_service_factory.h"
#include "chrome/browser/ash/app_list/arc/arc_app_list_prefs.h"
#include "chrome/browser/ash/crosapi/browser_util.h"
#include "chrome/browser/ash/file_manager/app_id.h"
#include "chrome/browser/ash/file_manager/prefs_migration_uma.h"
#include "chrome/browser/ash/login/demo_mode/demo_session.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/extensions/extension_keeplist_chromeos.h"
#include "chrome/browser/prefs/pref_service_syncable_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "chrome/browser/ui/ash/default_pinned_apps.h"
#include "chrome/browser/ui/ash/shelf/shelf_controller_helper.h"
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
#include "chrome/common/pref_names.h"
#include "components/app_constants/constants.h"
#include "components/crx_file/id_util.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_registry.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/services/app_service/public/cpp/app_registry_cache.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/app_update.h"
#include "components/sync/base/user_selectable_type.h"
#include "components/sync/driver/sync_service.h"
#include "components/sync/driver/sync_user_settings.h"
#include "components/sync/model/string_ordinal.h"
#include "components/sync_preferences/pref_service_syncable.h"
#include "extensions/common/constants.h"
#include "url/gurl.h"
using syncer::UserSelectableOsType;
using syncer::UserSelectableType;
namespace {
// Returns pinned app position even if app is not currently visible on device
// that is leftmost item on the shelf. If |exclude_chrome| is true then Chrome
// app is not processed. if nothing pinned found, returns an invalid ordinal.
syncer::StringOrdinal GetFirstPinnedAppPosition(
app_list::AppListSyncableService* syncable_service,
bool exclude_chrome) {
syncer::StringOrdinal position;
for (const auto& sync_peer : syncable_service->sync_items()) {
if (!sync_peer.second->item_pin_ordinal.IsValid())
continue;
if (exclude_chrome && (sync_peer.first == app_constants::kChromeAppId ||
sync_peer.first == app_constants::kLacrosAppId)) {
continue;
}
if (!position.IsValid() ||
sync_peer.second->item_pin_ordinal.LessThan(position)) {
position = sync_peer.second->item_pin_ordinal;
}
}
return position;
}
// Helper to create pin position that stays before any synced app, even if
// app is not currently visible on a device.
syncer::StringOrdinal CreateFirstPinPosition(
app_list::AppListSyncableService* syncable_service) {
const syncer::StringOrdinal position =
GetFirstPinnedAppPosition(syncable_service, false /* exclude_chrome */);
return position.IsValid() ? position.CreateBefore()
: syncer::StringOrdinal::CreateInitialOrdinal();
}
// Ensures |app_id| is pinned. If it is not pinned, makes it pinned in the first
// position.
void EnsurePinnedOrMakeFirst(
const std::string& app_id,
app_list::AppListSyncableService* syncable_service) {
syncer::StringOrdinal position = syncable_service->GetPinPosition(app_id);
if (!position.IsValid()) {
position = CreateFirstPinPosition(syncable_service);
syncable_service->SetPinPosition(app_id, position);
}
}
const char kDefaultPinnedAppsKey[] = "default";
// This is the default prefix for a chrome app loaded in the primary profile in
// Lacros. Note that "Default" is the name of the directory for the main profile
// in Lacros's --user-data-dir. This is fragile, but is hopefully only needed
// for a brief time while transitioning from ash chrome apps to Lacros chrome
// apps. Note that to support multi-profile chrome apps, we're going to need a
// profile-stable identifier anyways, in the near future.
const char kLacrosChromeAppPrefix[] = "Default###";
bool skip_pinned_apps_from_sync_for_test = false;
std::vector<ash::ShelfID> AppIdsToShelfIDs(
const std::vector<std::string>& app_ids) {
std::vector<ash::ShelfID> shelf_ids(app_ids.size());
for (size_t i = 0; i < app_ids.size(); ++i)
shelf_ids[i] = ash::ShelfID(app_ids[i]);
return shelf_ids;
}
struct PinInfo {
PinInfo(const std::string& app_id, const syncer::StringOrdinal& item_ordinal)
: app_id(app_id), item_ordinal(item_ordinal) {}
std::string app_id;
syncer::StringOrdinal item_ordinal;
};
struct ComparePinInfo {
bool operator()(const PinInfo& pin1, const PinInfo& pin2) {
return pin1.item_ordinal.LessThan(pin2.item_ordinal);
}
};
// Helper function that returns the right pref string based on device type.
// This is required because tablet form factor devices do not sync app
// positions and pin preferences.
std::string GetShelfDefaultPinLayoutPref() {
if (ash::switches::IsTabletFormFactor())
return prefs::kShelfDefaultPinLayoutRollsForTabletFormFactor;
return prefs::kShelfDefaultPinLayoutRolls;
}
// Returns true in case default pin layout configuration could be applied
// safely. That means all required components are synced or worked in local
// mode.
bool IsSafeToApplyDefaultPinLayout(Profile* profile) {
const syncer::SyncService* sync_service =
SyncServiceFactory::GetForProfile(profile);
// No |sync_service| in incognito mode.
if (!sync_service)
return true;
// Tablet form-factor devices do not have position sync.
if (ash::switches::IsTabletFormFactor())
return true;
const syncer::SyncUserSettings* settings = sync_service->GetUserSettings();
// If App sync is not yet started, don't apply default pin apps once synced
// apps is likely override it. There is a case when App sync is disabled and
// in last case local cache is available immediately.
if (sync_service->IsSyncFeatureEnabled() &&
settings->GetSelectedOsTypes().Has(UserSelectableOsType::kOsApps) &&
!app_list::AppListSyncableServiceFactory::GetForProfile(profile)
->IsSyncing()) {
return false;
}
// If shelf pin layout rolls preference is not started yet then we cannot say
// if we rolled layout or not.
if (sync_service->IsSyncFeatureEnabled() &&
settings->GetSelectedOsTypes().Has(
UserSelectableOsType::kOsPreferences) &&
!PrefServiceSyncableFromProfile(profile)->AreOsPrefsSyncing()) {
return false;
}
return true;
}
} // namespace
const char ChromeShelfPrefs::kPinnedAppsPrefAppIDKey[] = "id";
ChromeShelfPrefs::ChromeShelfPrefs(Profile* profile) : profile_(profile) {}
ChromeShelfPrefs::~ChromeShelfPrefs() {
StopObservingSyncService();
}
void ChromeShelfPrefs::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterListPref(prefs::kPolicyPinnedLauncherApps);
registry->RegisterListPref(
prefs::kShelfDefaultPinLayoutRolls,
user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PRIORITY_PREF);
registry->RegisterListPref(
prefs::kShelfDefaultPinLayoutRollsForTabletFormFactor,
PrefRegistry::NO_REGISTRATION_FLAGS);
}
void ChromeShelfPrefs::InitLocalPref(PrefService* prefs,
const char* local,
const char* synced) {
// Ash's prefs *should* have been propagated to Chrome by now, but maybe not.
// This belongs in Ash, but it can't observe syncing changes: crbug.com/774657
if (prefs->FindPreference(local) && prefs->FindPreference(synced) &&
!prefs->FindPreference(local)->HasUserSetting()) {
prefs->SetString(local, prefs->GetString(synced));
}
}
// Helper that extracts app list from policy preferences.
std::vector<std::string> ChromeShelfPrefs::GetAppsPinnedByPolicy(
ShelfControllerHelper* helper) {
const base::Value::List& policy_apps =
GetPrefs()->GetList(prefs::kPolicyPinnedLauncherApps);
if (policy_apps.empty()) {
return {};
}
std::vector<std::string> policy_entries;
for (const auto& policy_app : policy_apps) {
if (!policy_app.is_dict()) {
continue;
}
const std::string* policy_entry = policy_app.GetDict().FindString(
ChromeShelfPrefs::kPinnedAppsPrefAppIDKey);
if (!policy_entry) {
LOG(ERROR) << "Cannot extract policy app info from prefs.";
continue;
}
if (ash::DemoSession::Get() &&
!ash::DemoSession::Get()->ShouldShowAndroidOrChromeAppInShelf(
*policy_entry)) {
continue;
}
policy_entries.push_back(apps_util::TransformRawPolicyId(*policy_entry));
}
if (policy_entries.empty()) {
return {};
}
std::vector<std::string> results;
for (const auto& policy_entry : policy_entries) {
std::vector<std::string> app_ids =
apps_util::GetAppIdsFromPolicyId(helper->profile(), policy_entry);
if (app_ids.empty()) {
LOG(WARNING) << "No matching app(s) found for |policy_entry| = "
<< policy_entry;
continue;
}
base::Extend(results, std::move(app_ids));
}
return results;
}
// Helper to creates pin position that stays before any synced app, even if
// app is not currently visible on a device.
syncer::StringOrdinal CreateLastPinPosition(Profile* profile) {
syncer::StringOrdinal position;
app_list::AppListSyncableService* syncable_service =
app_list::AppListSyncableServiceFactory::GetForProfile(profile);
for (const auto& sync_peer : syncable_service->sync_items()) {
if (!sync_peer.second->item_pin_ordinal.IsValid())
continue;
if (!position.IsValid() ||
sync_peer.second->item_pin_ordinal.GreaterThan(position)) {
position = sync_peer.second->item_pin_ordinal;
}
}
return position.IsValid() ? position.CreateAfter()
: syncer::StringOrdinal::CreateInitialOrdinal();
}
// Helper to create and insert pins on the shelf for the set of apps defined in
// |app_ids| after Chrome in the first position and before any other pinned app.
// If Chrome is not the first pinned app then apps are pinned before any other
// app.
void InsertPinsAfterChromeAndBeforeFirstPinnedApp(
app_list::AppListSyncableService* syncable_service,
const std::vector<std::string>& app_ids) {
// Chrome must be pinned at this point.
syncer::StringOrdinal chrome_position =
syncable_service->GetPinPosition(app_constants::kChromeAppId);
DCHECK(chrome_position.IsValid());
// New pins are inserted after this position.
syncer::StringOrdinal after;
// New pins are inserted before this position.
syncer::StringOrdinal before =
GetFirstPinnedAppPosition(syncable_service, true /* exclude_chrome */);
if (!before.IsValid()) {
before = chrome_position.CreateAfter();
}
if (before.GreaterThan(chrome_position)) {
// Perfect case, Chrome is the first pinned app and we have next pinned app.
after = chrome_position;
} else {
after = before.CreateBefore();
}
// When chrome and lacros are enabled side-by-side, try positioning default
// apps after lacros icon.
if (crosapi::browser_util::IsLacrosEnabled()) {
syncer::StringOrdinal lacros_position =
syncable_service->GetPinPosition(app_constants::kLacrosAppId);
if (lacros_position.IsValid() && lacros_position.LessThan(before) &&
lacros_position.GreaterThan(after)) {
after = lacros_position;
}
}
for (const auto& app_id : app_ids) {
// Check if we already processed the current app.
if (syncable_service->GetPinPosition(app_id).IsValid())
continue;
const syncer::StringOrdinal position = after.CreateBetween(before);
syncable_service->SetPinPosition(app_id, position);
// Shift after position, next policy pin position will be created after
// current item.
after = position;
}
}
std::vector<ash::ShelfID> ChromeShelfPrefs::GetPinnedAppsFromSync(
ShelfControllerHelper* helper) {
PrefService* prefs = GetPrefs();
app_list::AppListSyncableService* const syncable_service =
GetSyncableService();
// Some unit tests may not have it or service may not be initialized.
if (!syncable_service || !syncable_service->IsInitialized() ||
skip_pinned_apps_from_sync_for_test) {
return std::vector<ash::ShelfID>();
}
ObserveSyncService();
if (ShouldPerformConsistencyMigrations()) {
needs_consistency_migrations_ = false;
MigrateFilesChromeAppToSWA(syncable_service);
EnsureChromePinned(syncable_service);
}
// This migration must be run outside of the consistency migrations block
// since the timing can occur later, after apps have been synced.
if (!DidAddDefaultApps(prefs) && ShouldAddDefaultApps(prefs)) {
AddDefaultApps(prefs, syncable_service);
}
// Handle pins, forced by policy. In case Chrome is first app they are added
// after Chrome, otherwise they are added to the front. Note, we handle apps
// that may not be currently on device. At this case pin position would be
// preallocated and apps will appear on shelf in deterministic order, even if
// their install order differ.
InsertPinsAfterChromeAndBeforeFirstPinnedApp(syncable_service,
GetAppsPinnedByPolicy(helper));
// If Lacros is enabled and allowed for this user type, ensure the Lacros icon
// is pinned. Lacros doesn't support multi-signin, so only add the icon for
// the primary user.
if (crosapi::browser_util::IsLacrosEnabled() &&
ash::ProfileHelper::IsPrimaryProfile(profile_)) {
syncer::StringOrdinal lacros_position =
syncable_service->GetPinPosition(app_constants::kLacrosAppId);
if (!lacros_position.IsValid()) {
// If Lacros isn't already pinned, add it to the right of the Chrome icon.
InsertPinsAfterChromeAndBeforeFirstPinnedApp(
syncable_service, {app_constants::kLacrosAppId});
}
}
std::vector<PinInfo> pin_infos;
// Empty pins indicates that sync based pin model is used for the first
// time. In the normal workflow we have at least Chrome browser pin info.
for (const auto& sync_peer : syncable_service->sync_items()) {
// A null ordinal means the item has been unpinned.
if (!sync_peer.second->item_pin_ordinal.IsValid())
continue;
// kLacrosAppId is only valid when side-by-side Lacros is enabled. When
// either lacros or ash is the only browser, kChromeAppId is the only valid
// sync ID for the browser.
bool lacros_side_by_side = crosapi::browser_util::IsLacrosEnabled() &&
crosapi::browser_util::IsAshWebBrowserEnabled();
if (!lacros_side_by_side && sync_peer.first == app_constants::kLacrosAppId)
continue;
std::string app_id = GetShelfId(sync_peer.first);
// All sync items must be valid app service apps to be added to the shelf
// with the exception of ash-chrome, which for legacy reasons does not use
// the app service.
bool is_ash_chrome = app_id == app_constants::kChromeAppId;
if (!is_ash_chrome && !helper->IsValidIDForCurrentUser(app_id))
continue;
pin_infos.emplace_back(std::move(app_id),
sync_peer.second->item_pin_ordinal);
}
// Sort pins according their ordinals.
std::sort(pin_infos.begin(), pin_infos.end(), ComparePinInfo());
// Convert to ShelfID array.
std::vector<std::string> pins(pin_infos.size());
for (size_t i = 0; i < pin_infos.size(); ++i)
pins[i] = pin_infos[i].app_id;
return AppIdsToShelfIDs(pins);
}
void ChromeShelfPrefs::RemovePinPosition(Profile* profile,
const ash::ShelfID& shelf_id) {
DCHECK(profile);
const std::string& app_id = shelf_id.app_id;
if (!shelf_id.launch_id.empty()) {
VLOG(2) << "Syncing remove pin for '" << app_id
<< "' with non-empty launch id '" << shelf_id.launch_id
<< "' is not supported.";
return;
}
DCHECK(!app_id.empty());
// There currently exists a side-case that only exists in lacros side-by-side
// where ash-chrome can be unpinned. This won't occur in the long-term because
// lacros will be the only browser. Until then, explicitly disallow pinning of
// ash-chrome here. This is simpler than letter the logic leak into various
// parts of shelf and context menu code.
if (app_id == app_constants::kChromeAppId) {
LOG(ERROR) << "ash cannot be unpinned";
return;
}
app_list::AppListSyncableService* syncable_service =
app_list::AppListSyncableServiceFactory::GetForProfile(profile);
syncable_service->SetPinPosition(app_id, syncer::StringOrdinal());
}
void ChromeShelfPrefs::SetPinPosition(
const ash::ShelfID& shelf_id,
const ash::ShelfID& shelf_id_before,
const std::vector<ash::ShelfID>& shelf_ids_after) {
const std::string app_id = GetSyncId(shelf_id.app_id);
// No sync ids should begin with the lacros prefix, as it isn't stable.
DCHECK(!base::StartsWith(app_id, kLacrosChromeAppPrefix));
if (!shelf_id.launch_id.empty()) {
VLOG(2) << "Syncing set pin for '" << app_id
<< "' with non-empty launch id '" << shelf_id.launch_id
<< "' is not supported.";
return;
}
const std::string app_id_before = GetSyncId(shelf_id_before.app_id);
DCHECK(!app_id.empty());
DCHECK_NE(app_id, app_id_before);
app_list::AppListSyncableService* syncable_service = GetSyncableService();
// Some unit tests may not have this service.
if (!syncable_service)
return;
syncer::StringOrdinal position_before =
app_id_before.empty() ? syncer::StringOrdinal()
: syncable_service->GetPinPosition(app_id_before);
syncer::StringOrdinal position_after;
for (const auto& shelf_id_after : shelf_ids_after) {
std::string app_id_after = GetSyncId(shelf_id_after.app_id);
DCHECK_NE(app_id_after, app_id);
DCHECK_NE(app_id_after, app_id_before);
syncer::StringOrdinal position =
syncable_service->GetPinPosition(app_id_after);
DCHECK(position.IsValid());
if (!position.IsValid()) {
LOG(ERROR) << "Sync pin position was not found for " << app_id_after;
continue;
}
if (!position_before.IsValid() || !position.Equals(position_before)) {
position_after = position;
break;
}
}
syncer::StringOrdinal pin_position;
if (position_before.IsValid() && position_after.IsValid())
pin_position = position_before.CreateBetween(position_after);
else if (position_before.IsValid())
pin_position = position_before.CreateAfter();
else if (position_after.IsValid())
pin_position = position_after.CreateBefore();
else
pin_position = syncer::StringOrdinal::CreateInitialOrdinal();
syncable_service->SetPinPosition(app_id, pin_position);
}
void ChromeShelfPrefs::SkipPinnedAppsFromSyncForTest() {
skip_pinned_apps_from_sync_for_test = true;
}
void ChromeShelfPrefs::MigrateFilesChromeAppToSWA(
app_list::AppListSyncableService* syncable_service) {
if (GetPrefs()->GetBoolean(ash::prefs::kFilesAppUIPrefsMigrated)) {
return;
}
// Avoid migrating the user prefs (even if the migration fails) to avoid
// overriding preferences that a user may set on the SWA explicitly.
GetPrefs()->SetBoolean(ash::prefs::kFilesAppUIPrefsMigrated, true);
using MigrationStatus = file_manager::FileManagerPrefsMigrationStatus;
if (!syncable_service->GetSyncItem(extension_misc::kFilesManagerAppId)) {
base::UmaHistogramEnumeration(file_manager::kPrefsMigrationStatusUMA,
MigrationStatus::kFailNoExistingPreferences);
return;
}
if (!syncable_service->TransferItemAttributes(
/*from_app=*/extension_misc::kFilesManagerAppId,
/*to_app=*/file_manager::kFileManagerSwaAppId)) {
base::UmaHistogramEnumeration(file_manager::kPrefsMigrationStatusUMA,
MigrationStatus::kFailMigratingPreferences);
return;
}
base::UmaHistogramEnumeration(file_manager::kPrefsMigrationStatusUMA,
MigrationStatus::kSuccess);
}
void ChromeShelfPrefs::EnsureChromePinned(
app_list::AppListSyncableService* syncable_service) {
// If ash is the only web browser or if lacros is the only web browser, ensure
// that ash-chrome is pinned. The sync<->shelf translation layer ensures that
// we will use the appropriate shelf id.
if (!crosapi::browser_util::IsLacrosEnabled() ||
!crosapi::browser_util::IsAshWebBrowserEnabled()) {
EnsurePinnedOrMakeFirst(app_constants::kChromeAppId, syncable_service);
return;
}
// Otherwise, we are in a transition situation where both the ash and lacros
// web browsers are available. To ensure consistency with legacy behavior, we
// ensure both web browsers are pinned.
EnsurePinnedOrMakeFirst(app_constants::kLacrosAppId, syncable_service);
EnsurePinnedOrMakeFirst(app_constants::kChromeAppId, syncable_service);
}
bool ChromeShelfPrefs::DidAddDefaultApps(PrefService* pref_service) {
const auto& layouts_rolled =
pref_service->GetList(GetShelfDefaultPinLayoutPref());
return !layouts_rolled.empty();
}
bool ChromeShelfPrefs::ShouldAddDefaultApps(PrefService* pref_service) {
// Apply default apps in case profile syncing is done. Otherwise there is a
// risk that applied default apps would be overwritten by sync once it is
// completed. prefs::kPolicyPinnedLauncherApps overrides any default layout.
// This also limits applying experimental configuration only for users who
// have the default pin layout specified by |kDefaultPinnedApps| or for
// fresh users who have no pin information at all. Default configuration is
// not applied if any of experimental layout was rolled.
return !pref_service->HasPrefPath(prefs::kPolicyPinnedLauncherApps) &&
IsSafeToApplyDefaultPinLayout(profile_);
}
void ChromeShelfPrefs::AddDefaultApps(
PrefService* pref_service,
app_list::AppListSyncableService* syncable_service) {
VLOG(1) << "Roll default shelf pin layout " << kDefaultPinnedAppsKey;
std::vector<std::string> default_app_ids;
for (const char* default_app_id : GetDefaultPinnedAppsForFormFactor())
default_app_ids.push_back(default_app_id);
InsertPinsAfterChromeAndBeforeFirstPinnedApp(syncable_service,
default_app_ids);
ScopedListPrefUpdate update(pref_service, GetShelfDefaultPinLayoutPref());
update->Append(kDefaultPinnedAppsKey);
}
void ChromeShelfPrefs::AttachProfile(Profile* profile) {
profile_ = profile;
needs_consistency_migrations_ = true;
StopObservingSyncService();
}
bool ChromeShelfPrefs::ShouldPerformConsistencyMigrations() const {
return needs_consistency_migrations_;
}
app_list::AppListSyncableService* ChromeShelfPrefs::GetSyncableService() {
return app_list::AppListSyncableServiceFactory::GetForProfile(profile_);
}
PrefService* ChromeShelfPrefs::GetPrefs() {
return profile_->GetPrefs();
}
void ChromeShelfPrefs::ObserveSyncService() {
if (!observed_sync_service_) {
observed_sync_service_ = GetSyncableService();
observed_sync_service_->AddObserverAndStart(this);
}
}
bool ChromeShelfPrefs::IsStandaloneBrowserPublishingChromeApps() {
return crosapi::browser_util::IsLacrosChromeAppsEnabled();
}
apps::AppType ChromeShelfPrefs::GetAppType(const std::string& app_id) {
apps::AppServiceProxy* proxy =
apps::AppServiceProxyFactory::GetForProfile(profile_);
return proxy->AppRegistryCache().GetAppType(app_id);
}
bool ChromeShelfPrefs::IsAshExtensionApp(const std::string& app_id) {
bool should_run_in_lacros = false;
apps::AppServiceProxy* proxy =
apps::AppServiceProxyFactory::GetForProfile(profile_);
proxy->AppRegistryCache().ForOneApp(
app_id, [&should_run_in_lacros](const apps::AppUpdate& update) {
should_run_in_lacros = update.IsPlatformApp().value_or(false);
});
return should_run_in_lacros;
}
bool ChromeShelfPrefs::IsAshKeepListApp(const std::string& app_id) {
return extensions::ExtensionAppRunsInOS(app_id);
}
std::string ChromeShelfPrefs::GetShelfId(const std::string& sync_id) {
// If Lacros is the only web browser, transform the sync_id kChromeAppId to
// the shelf id kLacrosAppId.
if (!crosapi::browser_util::IsAshWebBrowserEnabled() &&
sync_id == app_constants::kChromeAppId) {
return app_constants::kLacrosAppId;
}
// No sync ids should begin with the lacros prefix, as it isn't stable.
DCHECK(!base::StartsWith(sync_id, kLacrosChromeAppPrefix));
// No muxing is necessary.
if (!apps::ShouldMuxExtensionIds()) {
return sync_id;
}
// If Lacros is not publishing chrome apps, no transformation is necessary.
if (!IsStandaloneBrowserPublishingChromeApps())
return sync_id;
// If this app is on the ash keep list, immediately return it. Even if there's
// a lacros chrome app that matches this id, we still want to use the ash
// version.
if (extensions::ExtensionAppRunsInOS(sync_id))
return sync_id;
// All the muxing code can be removed once Ash is past M104.
std::string transformed_app_id = kLacrosChromeAppPrefix + sync_id;
// If this is an ash extension app, we add a fixed prefix. This is based on
// the logic in lacros_extensions_util and is not version-skew stable.
if (IsAshExtensionApp(sync_id)) {
return transformed_app_id;
}
// Now we have to check if the sync id corresponds to a lacros extension app.
if (GetAppType(transformed_app_id) ==
apps::AppType::kStandaloneBrowserChromeApp) {
return transformed_app_id;
}
// This is not an extension app for either ash or Lacros.
return sync_id;
}
std::string ChromeShelfPrefs::GetSyncId(const std::string& shelf_id) {
// If Lacros is the only web browser, transform the shelf id kLacrosAppId to
// the sync_id kChromeAppId.
if (!crosapi::browser_util::IsAshWebBrowserEnabled() &&
shelf_id == app_constants::kLacrosAppId) {
return app_constants::kChromeAppId;
}
// No muxing is necessary.
if (!apps::ShouldMuxExtensionIds()) {
return shelf_id;
}
// All the muxing code can be removed once Ash is past M104.
// If Lacros is not publishing chrome apps, no transformation is necessary.
if (!IsStandaloneBrowserPublishingChromeApps())
return shelf_id;
// If this is a lacros chrome app, then we must remove the prefix.
std::string prefix_removed = shelf_id;
base::ReplaceFirstSubstringAfterOffset(&prefix_removed, /*start_offset=*/0,
kLacrosChromeAppPrefix, "");
apps::AppType type = GetAppType(shelf_id);
if (type == apps::AppType::kStandaloneBrowserChromeApp) {
return prefix_removed;
}
// If removing the prefix turns this into an ash chrome app, then we must
// remove the prefix.
type = GetAppType(prefix_removed);
if (type == apps::AppType::kChromeApp) {
return prefix_removed;
}
// Do not remove the prefix otherwise.
return shelf_id;
}
void ChromeShelfPrefs::OnSyncModelUpdated() {
needs_consistency_migrations_ = true;
}
void ChromeShelfPrefs::StopObservingSyncService() {
if (observed_sync_service_) {
observed_sync_service_->RemoveObserver(this);
observed_sync_service_ = nullptr;
}
}