| // Copyright (c) 2012 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/ash/chrome_launcher_prefs.h" |
| |
| #include <stddef.h> |
| |
| #include <set> |
| |
| #include "ash/public/cpp/app_launch_id.h" |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/values.h" |
| #include "chrome/browser/app_mode/app_mode_utils.h" |
| #include "chrome/browser/chromeos/arc/arc_support_host.h" |
| #include "chrome/browser/prefs/pref_service_syncable_util.h" |
| #include "chrome/browser/ui/app_list/app_list_syncable_service.h" |
| #include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h" |
| #include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h" |
| #include "chrome/browser/ui/ash/launcher/launcher_controller_helper.h" |
| #include "chrome/common/extensions/extension_constants.h" |
| #include "chrome/common/pref_names.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/prefs/scoped_user_pref_update.h" |
| #include "components/sync/model/string_ordinal.h" |
| #include "components/sync_preferences/pref_service_syncable.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/display/types/display_constants.h" |
| |
| namespace ash { |
| namespace launcher { |
| |
| namespace { |
| |
| std::unique_ptr<base::DictionaryValue> CreateAppDict( |
| const std::string& app_id) { |
| auto app_value = base::MakeUnique<base::DictionaryValue>(); |
| app_value->SetString(kPinnedAppsPrefAppIDPath, app_id); |
| return app_value; |
| } |
| |
| // App ID of default pinned apps. |
| const char* kDefaultPinnedApps[] = { |
| extension_misc::kGmailAppId, extension_misc::kGoogleDocAppId, |
| extension_misc::kYoutubeAppId, ArcSupportHost::kHostAppId}; |
| |
| std::unique_ptr<base::ListValue> CreateDefaultPinnedAppsList() { |
| std::unique_ptr<base::ListValue> apps(new base::ListValue); |
| for (size_t i = 0; i < arraysize(kDefaultPinnedApps); ++i) |
| apps->Append(CreateAppDict(kDefaultPinnedApps[i])); |
| |
| return apps; |
| } |
| |
| // Returns the preference value for the display with the given |display_id|. |
| // The pref value is stored in |local_path| and |path|, but the pref service may |
| // have per-display preferences and the value can be specified by policy. |
| // Here is the priority: |
| // * A value managed by policy. This is a single value that applies to all |
| // displays. |
| // * A user-set value for the specified display. |
| // * A user-set value in |local_path| or |path|, if no per-display settings are |
| // ever specified (see http://crbug.com/173719 for why). |local_path| is |
| // preferred. See comment in |kShelfAlignment| as to why we consider two |
| // prefs and why |local_path| is preferred. |
| // * A value recommended by policy. This is a single value that applies to all |
| // root windows. |
| // * The default value for |local_path| if the value is not recommended by |
| // policy. |
| std::string GetPerDisplayPref(PrefService* prefs, |
| int64_t display_id, |
| const char* local_path, |
| const char* path) { |
| const PrefService::Preference* local_pref = prefs->FindPreference(local_path); |
| const std::string value(prefs->GetString(local_path)); |
| if (local_pref->IsManaged()) |
| return value; |
| |
| std::string pref_key = base::Int64ToString(display_id); |
| bool has_per_display_prefs = false; |
| if (!pref_key.empty()) { |
| const base::DictionaryValue* shelf_prefs = |
| prefs->GetDictionary(prefs::kShelfPreferences); |
| const base::DictionaryValue* display_pref = nullptr; |
| std::string per_display_value; |
| if (shelf_prefs->GetDictionary(pref_key, &display_pref) && |
| display_pref->GetString(path, &per_display_value)) |
| return per_display_value; |
| |
| // If the pref for the specified display is not found, scan the whole prefs |
| // and check if the prefs for other display is already specified. |
| std::string unused_value; |
| for (base::DictionaryValue::Iterator iter(*shelf_prefs); !iter.IsAtEnd(); |
| iter.Advance()) { |
| const base::DictionaryValue* display_pref = nullptr; |
| if (iter.value().GetAsDictionary(&display_pref) && |
| display_pref->GetString(path, &unused_value)) { |
| has_per_display_prefs = true; |
| break; |
| } |
| } |
| } |
| |
| if (local_pref->IsRecommended() || !has_per_display_prefs) |
| return value; |
| |
| const base::Value* default_value = prefs->GetDefaultPrefValue(local_path); |
| std::string default_string; |
| default_value->GetAsString(&default_string); |
| return default_string; |
| } |
| |
| // Sets the preference value for the display with the given |display_id|. |
| void SetPerDisplayPref(PrefService* prefs, |
| int64_t display_id, |
| const char* pref_key, |
| const std::string& value) { |
| if (display_id < 0) |
| return; |
| |
| // Avoid DictionaryPrefUpdate's notifications for read but unmodified prefs. |
| const base::DictionaryValue* current_shelf_prefs = |
| prefs->GetDictionary(prefs::kShelfPreferences); |
| DCHECK(current_shelf_prefs); |
| std::string display_key = base::Int64ToString(display_id); |
| const base::DictionaryValue* current_display_prefs = nullptr; |
| std::string current_value; |
| if (current_shelf_prefs->GetDictionary(display_key, ¤t_display_prefs) && |
| current_display_prefs->GetString(pref_key, ¤t_value) && |
| current_value == value) { |
| return; |
| } |
| |
| DictionaryPrefUpdate update(prefs, prefs::kShelfPreferences); |
| base::DictionaryValue* shelf_prefs = update.Get(); |
| base::DictionaryValue* display_prefs = nullptr; |
| if (!shelf_prefs->GetDictionary(display_key, &display_prefs)) { |
| display_prefs = new base::DictionaryValue(); |
| shelf_prefs->Set(display_key, display_prefs); |
| } |
| display_prefs->SetStringWithoutPathExpansion(pref_key, value); |
| } |
| |
| ShelfAlignment AlignmentFromPref(const std::string& value) { |
| if (value == kShelfAlignmentLeft) |
| return SHELF_ALIGNMENT_LEFT; |
| else if (value == kShelfAlignmentRight) |
| return SHELF_ALIGNMENT_RIGHT; |
| // Default to bottom. |
| return SHELF_ALIGNMENT_BOTTOM; |
| } |
| |
| const char* AlignmentToPref(ShelfAlignment alignment) { |
| switch (alignment) { |
| case SHELF_ALIGNMENT_BOTTOM: |
| return kShelfAlignmentBottom; |
| case SHELF_ALIGNMENT_LEFT: |
| return kShelfAlignmentLeft; |
| case SHELF_ALIGNMENT_RIGHT: |
| return kShelfAlignmentRight; |
| case SHELF_ALIGNMENT_BOTTOM_LOCKED: |
| // This should not be a valid preference option for now. We only want to |
| // lock the shelf during login or when adding a user. |
| return nullptr; |
| } |
| NOTREACHED(); |
| return nullptr; |
| } |
| |
| ShelfAutoHideBehavior AutoHideBehaviorFromPref(const std::string& value) { |
| // Note: To maintain sync compatibility with old images of chrome/chromeos |
| // the set of values that may be encountered includes the now-extinct |
| // "Default" as well as "Never" and "Always", "Default" should now |
| // be treated as "Never" (http://crbug.com/146773). |
| if (value == kShelfAutoHideBehaviorAlways) |
| return SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS; |
| return SHELF_AUTO_HIDE_BEHAVIOR_NEVER; |
| } |
| |
| const char* AutoHideBehaviorToPref(ShelfAutoHideBehavior behavior) { |
| switch (behavior) { |
| case SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS: |
| return kShelfAutoHideBehaviorAlways; |
| case SHELF_AUTO_HIDE_BEHAVIOR_NEVER: |
| return kShelfAutoHideBehaviorNever; |
| case SHELF_AUTO_HIDE_ALWAYS_HIDDEN: |
| // This should not be a valid preference option for now. We only want to |
| // completely hide it when we run in app mode - or while we temporarily |
| // hide the shelf as part of an animation (e.g. the multi user change). |
| return nullptr; |
| } |
| NOTREACHED(); |
| return nullptr; |
| } |
| |
| bool IsAppIdArcPackage(const std::string& app_id) { |
| return app_id.find('.') != app_id.npos; |
| } |
| |
| std::vector<std::string> GetActivitiesForPackage( |
| const std::string& package, |
| const std::vector<std::string>& all_arc_app_ids, |
| const ArcAppListPrefs& app_list_pref) { |
| std::vector<std::string> activities; |
| for (const std::string& app_id : all_arc_app_ids) { |
| const std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = |
| app_list_pref.GetApp(app_id); |
| if (app_info->package_name == package) { |
| activities.push_back(app_info->activity); |
| } |
| } |
| return activities; |
| } |
| |
| // If no user-set value exists at |local_path|, the value from |synced_path| is |
| // copied to |local_path|. |
| void PropagatePrefToLocalIfNotSet( |
| sync_preferences::PrefServiceSyncable* pref_service, |
| const char* local_path, |
| const char* synced_path) { |
| if (!pref_service->FindPreference(local_path)->HasUserSetting()) |
| pref_service->SetString(local_path, pref_service->GetString(synced_path)); |
| } |
| |
| std::vector<AppLaunchId> AppIdsToAppLaunchIds( |
| const std::vector<std::string> app_ids) { |
| std::vector<AppLaunchId> app_launch_ids(app_ids.size(), AppLaunchId()); |
| for (size_t i = 0; i < app_ids.size(); ++i) |
| app_launch_ids[i] = AppLaunchId(app_ids[i]); |
| return app_launch_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 class to keep apps in order of appearance and to provide fast way |
| // to check if app exists in the list. |
| class AppTracker { |
| public: |
| bool HasApp(const std::string& app_id) const { |
| return app_set_.find(app_id) != app_set_.end(); |
| } |
| |
| void AddApp(const std::string& app_id) { |
| if (HasApp(app_id)) |
| return; |
| app_list_.push_back(app_id); |
| app_set_.insert(app_id); |
| } |
| |
| void MaybeAddApp(const std::string& app_id, |
| const LauncherControllerHelper* helper, |
| bool check_for_valid_app) { |
| DCHECK_NE(kPinnedAppsPlaceholder, app_id); |
| if (check_for_valid_app && !helper->IsValidIDForCurrentUser(app_id)) { |
| return; |
| } |
| AddApp(app_id); |
| } |
| |
| void MaybeAddAppFromPref(const base::DictionaryValue* app_pref, |
| const LauncherControllerHelper* helper, |
| bool check_for_valid_app) { |
| std::string app_id; |
| if (!app_pref->GetString(kPinnedAppsPrefAppIDPath, &app_id)) { |
| LOG(ERROR) << "Cannot get app id from app pref entry."; |
| return; |
| } |
| |
| if (app_id == kPinnedAppsPlaceholder) |
| return; |
| |
| bool pinned_by_policy = false; |
| if (app_pref->GetBoolean(kPinnedAppsPrefPinnedByPolicy, |
| &pinned_by_policy) && |
| pinned_by_policy) { |
| return; |
| } |
| |
| MaybeAddApp(app_id, helper, check_for_valid_app); |
| } |
| |
| const std::vector<std::string>& app_list() const { return app_list_; } |
| |
| private: |
| std::vector<std::string> app_list_; |
| std::set<std::string> app_set_; |
| }; |
| |
| } // namespace |
| |
| const char kPinnedAppsPrefAppIDPath[] = "id"; |
| const char kPinnedAppsPrefPinnedByPolicy[] = "pinned_by_policy"; |
| const char kPinnedAppsPlaceholder[] = "AppShelfIDPlaceholder--------"; |
| |
| const char kShelfAutoHideBehaviorAlways[] = "Always"; |
| const char kShelfAutoHideBehaviorNever[] = "Never"; |
| |
| const char kShelfAlignmentBottom[] = "Bottom"; |
| const char kShelfAlignmentLeft[] = "Left"; |
| const char kShelfAlignmentRight[] = "Right"; |
| |
| void RegisterChromeLauncherUserPrefs( |
| user_prefs::PrefRegistrySyncable* registry) { |
| // TODO: If we want to support multiple profiles this will likely need to be |
| // pushed to local state and we'll need to track profile per item. |
| registry->RegisterIntegerPref( |
| prefs::kShelfChromeIconIndex, |
| 0, |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| registry->RegisterListPref(prefs::kPinnedLauncherApps, |
| CreateDefaultPinnedAppsList(), |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| registry->RegisterListPref(prefs::kPolicyPinnedLauncherApps); |
| registry->RegisterStringPref(prefs::kShelfAutoHideBehavior, |
| kShelfAutoHideBehaviorNever, |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| registry->RegisterStringPref(prefs::kShelfAutoHideBehaviorLocal, |
| std::string()); |
| registry->RegisterStringPref(prefs::kShelfAlignment, |
| kShelfAlignmentBottom, |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| registry->RegisterStringPref(prefs::kShelfAlignmentLocal, std::string()); |
| registry->RegisterDictionaryPref(prefs::kShelfPreferences); |
| registry->RegisterIntegerPref(prefs::kLogoutDialogDurationMs, 20000); |
| registry->RegisterBooleanPref(prefs::kShowLogoutButtonInTray, false); |
| } |
| |
| ShelfAutoHideBehavior GetShelfAutoHideBehaviorPref(PrefService* prefs, |
| int64_t display_id) { |
| DCHECK_NE(display_id, display::kInvalidDisplayId); |
| |
| // Don't show the shelf in app mode. |
| if (chrome::IsRunningInAppMode()) |
| return SHELF_AUTO_HIDE_ALWAYS_HIDDEN; |
| |
| // See comment in |kShelfAlignment| as to why we consider two prefs. |
| return AutoHideBehaviorFromPref( |
| GetPerDisplayPref(prefs, display_id, prefs::kShelfAutoHideBehaviorLocal, |
| prefs::kShelfAutoHideBehavior)); |
| } |
| |
| void SetShelfAutoHideBehaviorPref(PrefService* prefs, |
| int64_t display_id, |
| ShelfAutoHideBehavior behavior) { |
| DCHECK_NE(display_id, display::kInvalidDisplayId); |
| |
| const char* value = AutoHideBehaviorToPref(behavior); |
| if (!value) |
| return; |
| |
| SetPerDisplayPref(prefs, display_id, prefs::kShelfAutoHideBehavior, value); |
| if (display_id == display::Screen::GetScreen()->GetPrimaryDisplay().id()) { |
| // See comment in |kShelfAlignment| about why we have two prefs here. |
| prefs->SetString(prefs::kShelfAutoHideBehaviorLocal, value); |
| prefs->SetString(prefs::kShelfAutoHideBehavior, value); |
| } |
| } |
| |
| ShelfAlignment GetShelfAlignmentPref(PrefService* prefs, int64_t display_id) { |
| DCHECK_NE(display_id, display::kInvalidDisplayId); |
| |
| // See comment in |kShelfAlignment| as to why we consider two prefs. |
| return AlignmentFromPref(GetPerDisplayPref( |
| prefs, display_id, prefs::kShelfAlignmentLocal, prefs::kShelfAlignment)); |
| } |
| |
| void SetShelfAlignmentPref(PrefService* prefs, |
| int64_t display_id, |
| ShelfAlignment alignment) { |
| DCHECK_NE(display_id, display::kInvalidDisplayId); |
| |
| const char* value = AlignmentToPref(alignment); |
| if (!value) |
| return; |
| |
| SetPerDisplayPref(prefs, display_id, prefs::kShelfAlignment, value); |
| if (display_id == display::Screen::GetScreen()->GetPrimaryDisplay().id()) { |
| // See comment in |kShelfAlignment| as to why we consider two prefs. |
| prefs->SetString(prefs::kShelfAlignmentLocal, value); |
| prefs->SetString(prefs::kShelfAlignment, value); |
| } |
| } |
| |
| // Helper that extracts app list from policy preferences. |
| void GetAppsPinnedByPolicy(const PrefService* prefs, |
| const LauncherControllerHelper* helper, |
| bool check_for_valid_app, |
| AppTracker* apps) { |
| DCHECK(apps); |
| DCHECK(apps->app_list().empty()); |
| |
| const auto* policy_apps = prefs->GetList(prefs::kPolicyPinnedLauncherApps); |
| if (!policy_apps) |
| return; |
| |
| // Obtain here all ids of ARC apps because it takes linear time, and getting |
| // them in the loop bellow would lead to quadratic complexity. |
| const ArcAppListPrefs* const arc_app_list_pref = helper->GetArcAppListPrefs(); |
| const std::vector<std::string> all_arc_app_ids( |
| arc_app_list_pref ? arc_app_list_pref->GetAppIds() |
| : std::vector<std::string>()); |
| |
| std::string app_id; |
| for (size_t i = 0; i < policy_apps->GetSize(); ++i) { |
| const base::DictionaryValue* dictionary = nullptr; |
| if (!policy_apps->GetDictionary(i, &dictionary) || |
| !dictionary->GetString(kPinnedAppsPrefAppIDPath, &app_id)) { |
| LOG(ERROR) << "Cannot extract policy app info from prefs."; |
| continue; |
| } |
| if (IsAppIdArcPackage(app_id)) { |
| if (!arc_app_list_pref) |
| continue; |
| |
| // We are dealing with package name, not with 32 characters ID. |
| const std::string& arc_package = app_id; |
| const std::vector<std::string> activities = GetActivitiesForPackage( |
| arc_package, all_arc_app_ids, *arc_app_list_pref); |
| for (const auto& activity : activities) { |
| const std::string arc_app_id = |
| ArcAppListPrefs::GetAppId(arc_package, activity); |
| apps->MaybeAddApp(arc_app_id, helper, check_for_valid_app); |
| } |
| } else { |
| apps->MaybeAddApp(app_id, helper, check_for_valid_app); |
| } |
| } |
| } |
| |
| std::vector<std::string> GetPinnedAppsFromPrefsLegacy( |
| const PrefService* prefs, |
| const LauncherControllerHelper* helper, |
| bool check_for_valid_app) { |
| // Adding the app list item to the list of items requires that the ID is not |
| // a valid and known ID for the extension system. The ID was constructed that |
| // way - but just to make sure... |
| DCHECK(!helper->IsValidIDForCurrentUser(kPinnedAppsPlaceholder)); |
| |
| const auto* pinned_apps = prefs->GetList(prefs::kPinnedLauncherApps); |
| |
| // Get the sanitized preference value for the index of the Chrome app icon. |
| const size_t chrome_icon_index = std::max<size_t>( |
| 0, std::min<size_t>(pinned_apps->GetSize(), |
| prefs->GetInteger(prefs::kShelfChromeIconIndex))); |
| |
| // Check if Chrome is in either of the the preferences lists. |
| std::unique_ptr<base::Value> chrome_app( |
| CreateAppDict(extension_misc::kChromeAppId)); |
| |
| AppTracker apps; |
| GetAppsPinnedByPolicy(prefs, helper, check_for_valid_app, &apps); |
| |
| std::string app_id; |
| for (size_t i = 0; i < pinned_apps->GetSize(); ++i) { |
| // We need to position the chrome icon relative to its place in the pinned |
| // preference list - even if an item of that list isn't shown yet. |
| if (i == chrome_icon_index) |
| apps.AddApp(extension_misc::kChromeAppId); |
| const base::DictionaryValue* app_pref = nullptr; |
| if (!pinned_apps->GetDictionary(i, &app_pref)) { |
| LOG(ERROR) << "There is no dictionary for app entry."; |
| continue; |
| } |
| apps.MaybeAddAppFromPref(app_pref, helper, check_for_valid_app); |
| } |
| |
| // If not added yet, the chrome item will be the last item in the list. |
| apps.AddApp(extension_misc::kChromeAppId); |
| return apps.app_list(); |
| } |
| |
| // static |
| std::unique_ptr<ChromeLauncherPrefsObserver> |
| ChromeLauncherPrefsObserver::CreateIfNecessary(Profile* profile) { |
| sync_preferences::PrefServiceSyncable* prefs = |
| PrefServiceSyncableFromProfile(profile); |
| if (!prefs->FindPreference(prefs::kShelfAlignmentLocal)->HasUserSetting() || |
| !prefs->FindPreference(prefs::kShelfAutoHideBehaviorLocal) |
| ->HasUserSetting()) { |
| return base::WrapUnique(new ChromeLauncherPrefsObserver(prefs)); |
| } |
| return nullptr; |
| } |
| |
| ChromeLauncherPrefsObserver::~ChromeLauncherPrefsObserver() { |
| prefs_->RemoveObserver(this); |
| } |
| |
| ChromeLauncherPrefsObserver::ChromeLauncherPrefsObserver( |
| sync_preferences::PrefServiceSyncable* prefs) |
| : prefs_(prefs) { |
| // This causes OnIsSyncingChanged to be called when the value of |
| // PrefService::IsSyncing() changes. |
| prefs_->AddObserver(this); |
| } |
| |
| void ChromeLauncherPrefsObserver::OnIsSyncingChanged() { |
| // If prefs have synced, copy the values from |synced_path| to |local_path| |
| // if the local values haven't already been set. |
| if (!prefs_->IsSyncing()) |
| return; |
| PropagatePrefToLocalIfNotSet(prefs_, prefs::kShelfAlignmentLocal, |
| prefs::kShelfAlignment); |
| PropagatePrefToLocalIfNotSet(prefs_, prefs::kShelfAutoHideBehaviorLocal, |
| prefs::kShelfAutoHideBehavior); |
| prefs_->RemoveObserver(this); |
| } |
| |
| // Helper to create pin position that stays before any synced app, even if |
| // app is not currently visible on a device. |
| syncer::StringOrdinal GetFirstPinPosition(Profile* profile) { |
| syncer::StringOrdinal position; |
| app_list::AppListSyncableService* app_service = |
| app_list::AppListSyncableServiceFactory::GetForProfile(profile); |
| for (const auto& sync_peer : app_service->sync_items()) { |
| if (!sync_peer.second->item_pin_ordinal.IsValid()) |
| continue; |
| if (!position.IsValid() || |
| sync_peer.second->item_pin_ordinal.LessThan(position)) { |
| position = sync_peer.second->item_pin_ordinal; |
| } |
| } |
| |
| return position.IsValid() ? position.CreateBefore() |
| : syncer::StringOrdinal::CreateInitialOrdinal(); |
| } |
| |
| // Helper to creates pin position that stays before any synced app, even if |
| // app is not currently visible on a device. |
| syncer::StringOrdinal GetLastPinPosition(Profile* profile) { |
| syncer::StringOrdinal position; |
| app_list::AppListSyncableService* app_service = |
| app_list::AppListSyncableServiceFactory::GetForProfile(profile); |
| for (const auto& sync_peer : app_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(); |
| } |
| |
| std::vector<std::string> ImportLegacyPinnedApps( |
| const PrefService* prefs, |
| LauncherControllerHelper* helper) { |
| const std::vector<std::string> legacy_pins_all = |
| GetPinnedAppsFromPrefsLegacy(prefs, helper, false); |
| DCHECK(!legacy_pins_all.empty()); |
| |
| app_list::AppListSyncableService* app_service = |
| app_list::AppListSyncableServiceFactory::GetForProfile(helper->profile()); |
| |
| std::vector<std::string> legacy_pins_valid; |
| syncer::StringOrdinal last_position = |
| syncer::StringOrdinal::CreateInitialOrdinal(); |
| // Convert to sync item record. |
| for (const auto& app_id : legacy_pins_all) { |
| DCHECK_NE(kPinnedAppsPlaceholder, app_id); |
| app_service->SetPinPosition(app_id, last_position); |
| last_position = last_position.CreateAfter(); |
| if (helper->IsValidIDForCurrentUser(app_id)) |
| legacy_pins_valid.push_back(app_id); |
| } |
| |
| // Now process default apps. |
| for (size_t i = 0; i < arraysize(kDefaultPinnedApps); ++i) { |
| const std::string& app_id = kDefaultPinnedApps[i]; |
| // Check if it is already imported. |
| if (app_service->GetPinPosition(app_id).IsValid()) |
| continue; |
| // Check if it is present but not in legacy pin. |
| if (helper->IsValidIDForCurrentUser(app_id)) |
| continue; |
| app_service->SetPinPosition(app_id, last_position); |
| last_position = last_position.CreateAfter(); |
| } |
| |
| return legacy_pins_valid; |
| } |
| |
| std::vector<AppLaunchId> GetPinnedAppsFromPrefs( |
| const PrefService* prefs, |
| LauncherControllerHelper* helper) { |
| app_list::AppListSyncableService* app_service = |
| app_list::AppListSyncableServiceFactory::GetForProfile(helper->profile()); |
| // Some unit tests may not have it or service may not be initialized. |
| if (!app_service || !app_service->IsInitialized()) |
| return std::vector<AppLaunchId>(); |
| |
| std::vector<PinInfo> pin_infos; |
| |
| // Empty pins indicates that sync based pin model is used for the first |
| // time. In normal workflow we have at least Chrome browser pin info. |
| bool first_run = true; |
| |
| for (const auto& sync_peer : app_service->sync_items()) { |
| if (!sync_peer.second->item_pin_ordinal.IsValid()) |
| continue; |
| |
| first_run = false; |
| // Don't include apps that currently do not exist on device. |
| if (sync_peer.first != extension_misc::kChromeAppId && |
| !helper->IsValidIDForCurrentUser(sync_peer.first)) { |
| continue; |
| } |
| |
| pin_infos.push_back( |
| PinInfo(sync_peer.first, sync_peer.second->item_pin_ordinal)); |
| } |
| |
| if (first_run) { |
| // Return default apps in case profile is not synced yet. |
| sync_preferences::PrefServiceSyncable* const pref_service_syncable = |
| PrefServiceSyncableFromProfile(helper->profile()); |
| if (!pref_service_syncable->IsSyncing()) |
| return AppIdsToAppLaunchIds( |
| GetPinnedAppsFromPrefsLegacy(prefs, helper, true)); |
| |
| // We need to import legacy pins model and convert it to sync based |
| // model. |
| return AppIdsToAppLaunchIds(ImportLegacyPinnedApps(prefs, helper)); |
| } |
| |
| // Sort pins according their ordinals. |
| std::sort(pin_infos.begin(), pin_infos.end(), ComparePinInfo()); |
| |
| AppTracker policy_apps; |
| GetAppsPinnedByPolicy(prefs, helper, true, &policy_apps); |
| |
| // Pinned by policy apps appear first, if they were not shown before. |
| syncer::StringOrdinal front_position = GetFirstPinPosition(helper->profile()); |
| std::vector<std::string>::const_reverse_iterator it; |
| for (it = policy_apps.app_list().rbegin(); |
| it != policy_apps.app_list().rend(); ++it) { |
| const std::string& app_id = *it; |
| if (app_id == kPinnedAppsPlaceholder) |
| continue; |
| |
| // Check if we already processed current app. |
| if (app_service->GetPinPosition(app_id).IsValid()) |
| continue; |
| |
| // Now move it to the front. |
| pin_infos.insert(pin_infos.begin(), PinInfo(*it, front_position)); |
| app_service->SetPinPosition(app_id, front_position); |
| front_position = front_position.CreateBefore(); |
| } |
| |
| // Now insert Chrome browser app if needed. |
| if (!app_service->GetPinPosition(extension_misc::kChromeAppId).IsValid()) { |
| pin_infos.insert(pin_infos.begin(), |
| PinInfo(extension_misc::kChromeAppId, front_position)); |
| app_service->SetPinPosition(extension_misc::kChromeAppId, front_position); |
| } |
| |
| if (helper->IsValidIDForCurrentUser(ArcSupportHost::kHostAppId)) { |
| if (!app_service->GetSyncItem(ArcSupportHost::kHostAppId)) { |
| const syncer::StringOrdinal arc_host_position = |
| GetLastPinPosition(helper->profile()); |
| pin_infos.insert(pin_infos.begin(), |
| PinInfo(ArcSupportHost::kHostAppId, arc_host_position)); |
| app_service->SetPinPosition(ArcSupportHost::kHostAppId, |
| arc_host_position); |
| } |
| } |
| |
| // Convert to AppLaunchId 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 AppIdsToAppLaunchIds(pins); |
| } |
| |
| void RemovePinPosition(Profile* profile, const AppLaunchId& app_launch_id) { |
| DCHECK(profile); |
| |
| const std::string& app_id = app_launch_id.app_id(); |
| if (!app_launch_id.launch_id().empty()) { |
| VLOG(2) << "Syncing remove pin for '" << app_id |
| << "' with non-empty launch id '" << app_launch_id.launch_id() |
| << "' is not supported."; |
| return; |
| } |
| DCHECK(!app_id.empty()); |
| |
| app_list::AppListSyncableService* app_service = |
| app_list::AppListSyncableServiceFactory::GetForProfile(profile); |
| app_service->SetPinPosition(app_id, syncer::StringOrdinal()); |
| } |
| |
| void SetPinPosition(Profile* profile, |
| const AppLaunchId& app_launch_id, |
| const AppLaunchId& app_launch_id_before, |
| const std::vector<AppLaunchId>& app_launch_ids_after) { |
| DCHECK(profile); |
| |
| const std::string& app_id = app_launch_id.app_id(); |
| if (!app_launch_id.launch_id().empty()) { |
| VLOG(2) << "Syncing set pin for '" << app_id |
| << "' with non-empty launch id '" << app_launch_id.launch_id() |
| << "' is not supported."; |
| return; |
| } |
| |
| const std::string& app_id_before = app_launch_id_before.app_id(); |
| |
| DCHECK(!app_id.empty()); |
| DCHECK_NE(app_id, app_id_before); |
| |
| app_list::AppListSyncableService* app_service = |
| app_list::AppListSyncableServiceFactory::GetForProfile(profile); |
| // Some unit tests may not have this service. |
| if (!app_service) |
| return; |
| |
| syncer::StringOrdinal position_before = |
| app_id_before.empty() ? syncer::StringOrdinal() |
| : app_service->GetPinPosition(app_id_before); |
| syncer::StringOrdinal position_after; |
| for (const auto& app_launch_id_after : app_launch_ids_after) { |
| const std::string& app_id_after = app_launch_id_after.app_id(); |
| DCHECK_NE(app_id_after, app_id); |
| DCHECK_NE(app_id_after, app_id_before); |
| syncer::StringOrdinal position = app_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(); |
| app_service->SetPinPosition(app_id, pin_position); |
| } |
| |
| } // namespace launcher |
| } // namespace ash |