Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3144)

Unified Diff: chrome/browser/ui/ash/chrome_launcher_prefs.cc

Issue 2055553004: arc: Support pinned apps across Arc-enabled and Arc-disabled platforms. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: rebased, comments addressed, removed item_pinned_by_policy Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/ui/ash/chrome_launcher_prefs.cc
diff --git a/chrome/browser/ui/ash/chrome_launcher_prefs.cc b/chrome/browser/ui/ash/chrome_launcher_prefs.cc
index a75cd55fb012608e30100176ae468df7b14923d5..bcf96ec704eae101fb8f12f0dddfab85f61f3e60 100644
--- a/chrome/browser/ui/ash/chrome_launcher_prefs.cc
+++ b/chrome/browser/ui/ash/chrome_launcher_prefs.cc
@@ -6,7 +6,7 @@
#include <stddef.h>
-#include <memory>
+#include <set>
#include "base/macros.h"
#include "base/strings/string_number_conversions.h"
@@ -15,6 +15,8 @@
#include "chrome/browser/chromeos/arc/arc_auth_service.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"
@@ -23,10 +25,12 @@
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/syncable_prefs/pref_service_syncable.h"
+#include "sync/api/string_ordinal.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
namespace ash {
+namespace launcher {
namespace {
@@ -203,6 +207,47 @@ void PropagatePrefToLocalIfNotSet(
pref_service->SetString(local_path, pref_service->GetString(synced_path));
}
+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 AppList {
xiyuan 2016/06/14 21:42:50 nit: Can we get a better name? AppList collides wi
khmel 2016/06/15 17:01:20 Agree, quite confusing :)
+ public:
+ AppList() {}
+ ~AppList() {}
stevenjb 2016/06/14 22:44:09 nit: In a file local helper class, we can omit the
khmel 2016/06/15 17:01:20 Done.
+
+ 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);
+ }
+
+ const std::vector<std::string>& app_list() const { return app_list_; }
+
+ private:
+ std::vector<std::string> app_list_;
+ std::set<std::string> app_set_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppList);
xiyuan 2016/06/15 17:26:25 Why removing this?
khmel 2016/06/15 17:29:14 As Steven recommended in comment above. I would pe
xiyuan 2016/06/15 17:30:22 I see. Never mind then. I am fine either way.
stevenjb 2016/06/15 17:53:52 I forget whether we can use DISALLOW without an ex
khmel 2016/06/15 18:26:22 I tested, we still need CTOR for DISALLOW, so skip
+};
+
} // namespace
const char kPinnedAppsPrefAppIDPath[] = "id";
@@ -304,29 +349,16 @@ void SetShelfAlignmentPref(PrefService* prefs,
}
}
-std::vector<std::string> GetPinnedAppsFromPrefs(
- const PrefService* prefs,
- const LauncherControllerHelper* helper) {
- // 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));
+// Helper that extracts app list from policy preferences.
+void GetAppsPinnedByPolicy(const PrefService* prefs,
+ const LauncherControllerHelper* helper,
+ AppList* apps) {
+ DCHECK(apps);
+ DCHECK(apps->app_list().empty());
- std::vector<std::string> apps;
- const auto* pinned = prefs->GetList(prefs::kPinnedLauncherApps);
const auto* policy = prefs->GetList(prefs::kPolicyPinnedLauncherApps);
-
- // 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->GetSize(),
- prefs->GetInteger(prefs::kShelfChromeIconIndex)));
-
- // Check if Chrome is in either of the the preferences lists.
- std::unique_ptr<base::Value> chrome_app(
- ash::CreateAppDict(extension_misc::kChromeAppId));
- bool chrome_listed =
- (pinned->Find(*chrome_app.get()) != pinned->end() ||
- (policy && policy->Find(*chrome_app.get()) != policy->end()));
+ if (!policy)
+ 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.
@@ -336,11 +368,10 @@ std::vector<std::string> GetPinnedAppsFromPrefs(
: std::vector<std::string>());
std::string app_id;
- for (size_t i = 0; policy && (i < policy->GetSize()); ++i) {
+ for (size_t i = 0; i < policy->GetSize(); ++i) {
const base::DictionaryValue* dictionary = nullptr;
if (policy->GetDictionary(i, &dictionary) &&
- dictionary->GetString(kPinnedAppsPrefAppIDPath, &app_id) &&
- std::find(apps.begin(), apps.end(), app_id) == apps.end()) {
+ dictionary->GetString(kPinnedAppsPrefAppIDPath, &app_id)) {
if (IsAppIdArcPackage(app_id)) {
if (!arc_app_list_pref)
continue;
@@ -353,48 +384,59 @@ std::vector<std::string> GetPinnedAppsFromPrefs(
const std::string arc_app_id =
ArcAppListPrefs::GetAppId(arc_package, activity);
if (helper->IsValidIDForCurrentUser(arc_app_id))
- apps.push_back(arc_app_id);
+ apps->AddApp(arc_app_id);
}
} else if (helper->IsValidIDForCurrentUser(app_id)) {
- apps.push_back(app_id);
+ apps->AddApp(app_id);
}
}
}
+}
stevenjb 2016/06/14 22:44:08 nit: This could be a member of AppList.
khmel 2016/06/15 17:01:19 Done.
+
+std::vector<std::string> GetPinnedAppsFromPrefsLegacy(
+ const PrefService* prefs,
+ const LauncherControllerHelper* helper) {
+ // 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 = prefs->GetList(prefs::kPinnedLauncherApps);
stevenjb 2016/06/14 22:44:09 nit: pinned_apps
khmel 2016/06/15 17:01:20 Done.
+
+ // 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->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));
+
+ AppList apps;
+ GetAppsPinnedByPolicy(prefs, helper, &apps);
+
+ std::string app_id;
for (size_t i = 0; i < pinned->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 && !chrome_listed) {
- apps.push_back(extension_misc::kChromeAppId);
- chrome_listed = true;
- }
+ if (i == chrome_icon_index)
+ apps.AddApp(extension_misc::kChromeAppId);
bool pinned_by_policy = false;
const base::DictionaryValue* dictionary = nullptr;
if (pinned->GetDictionary(i, &dictionary) &&
dictionary->GetString(kPinnedAppsPrefAppIDPath, &app_id) &&
+ app_id != kPinnedAppsPlaceholder &&
helper->IsValidIDForCurrentUser(app_id) &&
- std::find(apps.begin(), apps.end(), app_id) == apps.end() &&
(!dictionary->GetBoolean(kPinnedAppsPrefPinnedByPolicy,
&pinned_by_policy) ||
!pinned_by_policy)) {
- apps.push_back(app_id);
+ apps.AddApp(app_id);
}
stevenjb 2016/06/14 22:44:09 nit: This would be a bit clearer as: const base::
khmel 2016/06/15 17:01:20 Done.
}
- if (arc::ArcAuthService::IsAllowedForProfile(helper->profile()) &&
- helper->IsValidIDForCurrentUser(ArcSupportHost::kHostAppId)) {
- apps.push_back(ArcSupportHost::kHostAppId);
- }
-
// If not added yet, the chrome item will be the last item in the list.
- if (!chrome_listed)
- apps.push_back(extension_misc::kChromeAppId);
-
- // If not added yet, place the app list item at the beginning of the list.
- if (std::find(apps.begin(), apps.end(), kPinnedAppsPlaceholder) == apps.end())
- apps.insert(apps.begin(), kPinnedAppsPlaceholder);
-
- return apps;
+ apps.AddApp(extension_misc::kChromeAppId);
+ return apps.app_list();
}
// static
@@ -434,4 +476,242 @@ void ChromeLauncherPrefsObserver::OnIsSyncingChanged() {
}
}
+// Helper to create pin position that stays before any synced app, even if
+// app is not currently visbile on a device.
+syncer::StringOrdinal CreateFirstPinPosition(Profile* profile) {
stevenjb 2016/06/14 22:44:09 nit: GetFirstPinPosition s/create/get/ in the comm
khmel 2016/06/15 17:01:20 Done.
+ 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 visbile on a device.
+syncer::StringOrdinal CreateLastPinPosition(Profile* profile) {
stevenjb 2016/06/14 22:44:09 Ditto
khmel 2016/06/15 17:01:20 Done.
+ 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 AppList& policy_apps) {
+ std::vector<std::string> legacy_pins =
+ GetPinnedAppsFromPrefsLegacy(prefs, helper);
+ DCHECK(!legacy_pins.empty());
+
+ app_list::AppListSyncableService* app_service =
+ app_list::AppListSyncableServiceFactory::GetForProfile(helper->profile());
+
+ syncer::StringOrdinal last_position =
+ syncer::StringOrdinal::CreateInitialOrdinal();
+ // Convert to sync item record.
+ for (const auto& app_id : legacy_pins) {
+ DCHECK_NE(kPinnedAppsPlaceholder, app_id);
+ app_service->SetPinPosition(app_id, last_position);
+ last_position = last_position.CreateAfter();
+ }
+
+ // 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;
+}
+
+std::vector<std::string> GetPinnedAppsFromPrefs(
+ const PrefService* prefs,
+ LauncherControllerHelper* helper) {
+ app_list::AppListSyncableService* app_service =
+ app_list::AppListSyncableServiceFactory::GetForProfile(helper->profile());
+ // Some unit tests may not have it.
+ if (!app_service)
+ return std::vector<std::string>();
+
+ std::vector<PinInfo> pin_infos;
+
+ AppList policy_apps;
+ GetAppsPinnedByPolicy(prefs, helper, &policy_apps);
+
+ // 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) {
+ // We need to import legacy pins model and convert it to sync based
+ // model.
+ return ImportLegacyPinnedApps(prefs, helper, policy_apps);
+ }
+
+ // Sort pins according their ordinals.
+ std::sort(pin_infos.begin(), pin_infos.end(), ComparePinInfo());
+
+ // Now insert Chrome browser app if needed.
+ syncer::StringOrdinal chrome_position =
+ app_service->GetPinPosition(extension_misc::kChromeAppId);
+ if (!chrome_position.IsValid()) {
+ chrome_position = CreateFirstPinPosition(helper->profile());
+ pin_infos.insert(pin_infos.begin(),
+ PinInfo(extension_misc::kChromeAppId, chrome_position));
+ app_service->SetPinPosition(extension_misc::kChromeAppId, chrome_position);
+ }
+
+ // Policy apps must preserve order of appearance and be first app in the list.
+ // Validate that there have a correct position and fix if needed.
stevenjb 2016/06/14 22:44:08 s/there/they/
khmel 2016/06/15 17:01:20 Done.
+ size_t shelf_index = 0;
+ for (const auto& app_id : policy_apps.app_list()) {
xiyuan 2016/06/14 21:42:50 Would this be enforced every time? If a user moves
stevenjb 2016/06/14 22:44:09 If we put the policy pinned apps in front every ti
khmel 2016/06/15 17:01:19 This is was done for unmovable pinned by policy ap
+ if (app_id == kPinnedAppsPlaceholder)
+ continue;
+ if (app_id == extension_misc::kChromeAppId)
+ continue;
+
+ // Chrome browser app has right to appear between pinned by policy apps.
+ if (shelf_index < pin_infos.size() &&
+ pin_infos[shelf_index].app_id == extension_misc::kChromeAppId) {
+ ++shelf_index;
+ }
+
+ const bool need_position_item = shelf_index >= pin_infos.size() ||
+ app_id != pin_infos[shelf_index].app_id;
+ if (need_position_item) {
+ // Remove existing app that breaks the order from the list.
+ if (shelf_index < pin_infos.size()) {
+ pin_infos.erase(
+ std::remove_if(pin_infos.begin() + shelf_index + 1, pin_infos.end(),
+ [app_id](const PinInfo& pin_info) {
+ return (pin_info.app_id == app_id);
+ }),
+ pin_infos.end());
+ }
+
+ syncer::StringOrdinal new_shelf_ordinal;
+ if (shelf_index == 0) {
+ // First app.
+ new_shelf_ordinal = pin_infos.empty()
+ ? syncer::StringOrdinal::CreateInitialOrdinal()
+ : pin_infos.front().item_ordinal.CreateBefore();
+ } else if (shelf_index == pin_infos.size()) {
+ new_shelf_ordinal = pin_infos.back().item_ordinal.CreateAfter();
+ } else {
+ new_shelf_ordinal =
+ pin_infos[shelf_index - 1].item_ordinal.CreateBetween(
+ pin_infos[shelf_index].item_ordinal);
+ }
+ pin_infos.insert(pin_infos.begin() + shelf_index,
+ PinInfo(app_id, new_shelf_ordinal));
+ app_service->SetPinPosition(app_id, new_shelf_ordinal);
+ }
+ ++shelf_index;
+ }
+
+ if (arc::ArcAuthService::IsAllowedForProfile(helper->profile()) &&
+ helper->IsValidIDForCurrentUser(ArcSupportHost::kHostAppId)) {
+ if (!app_service->GetSyncItem(ArcSupportHost::kHostAppId)) {
+ const syncer::StringOrdinal arc_host_position =
+ CreateLastPinPosition(helper->profile());
+ pin_infos.insert(pin_infos.begin(),
+ PinInfo(ArcSupportHost::kHostAppId, arc_host_position));
+ app_service->SetPinPosition(ArcSupportHost::kHostAppId,
+ arc_host_position);
+ }
+ }
+
+ // Convert to string 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 pins;
+}
+
+void RemovePinPosition(Profile* profile, const std::string& app_id) {
+ DCHECK(profile);
+ 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 std::string& app_id,
+ const std::string& app_id_before,
+ const std::string& app_id_after) {
+ DCHECK(profile);
+ DCHECK(!app_id.empty());
+ DCHECK_NE(app_id, app_id_before);
+ DCHECK_NE(app_id, app_id_after);
+ DCHECK(app_id_before.empty() || app_id_before != app_id_after);
+
+ 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 =
+ app_id_after.empty() ? syncer::StringOrdinal()
+ : app_service->GetPinPosition(app_id_after);
+
+ 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

Powered by Google App Engine
This is Rietveld 408576698