Commit 9ebc269c authored by Timothy Loh's avatar Timothy Loh Committed by Commit Bot

Add basic search for Crostini apps

Add support for searching in the App List for Crostini Apps. This only
does a search over app names, not e.g. executable names or keywords in
the desktop file. As we use the AppSearchProvider, this also makes apps
show up in the app list's list of recently launched apps.

Prior to first use, the Terminal app will only show up upon searching
for exactly the string "terminal" (case insensitive). The app list is
also updated to have the Terminal not appear if it hasn't been used yet.

Bug: 836137
Change-Id: I72b1f530d8e4812f0d13ba1fd45a0967458393ab
Reviewed-on: https://chromium-review.googlesource.com/1025496
Commit-Queue: Timothy Loh <timloh@chromium.org>
Reviewed-by: default avatarNicholas Verne <nverne@chromium.org>
Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#554355}
parent 572855c6
...@@ -80,6 +80,10 @@ bool IsCrostiniUIAllowedForProfile(Profile* profile) { ...@@ -80,6 +80,10 @@ bool IsCrostiniUIAllowedForProfile(Profile* profile) {
base::FeatureList::IsEnabled(features::kExperimentalCrostiniUI); base::FeatureList::IsEnabled(features::kExperimentalCrostiniUI);
} }
bool IsCrostiniEnabled(Profile* profile) {
return profile->GetPrefs()->GetBoolean(crostini::prefs::kCrostiniEnabled);
}
void LaunchCrostiniApp(Profile* profile, const std::string& app_id) { void LaunchCrostiniApp(Profile* profile, const std::string& app_id) {
auto* crostini_manager = crostini::CrostiniManager::GetInstance(); auto* crostini_manager = crostini::CrostiniManager::GetInstance();
crostini::CrostiniRegistryService* registry_service = crostini::CrostiniRegistryService* registry_service =
...@@ -89,7 +93,7 @@ void LaunchCrostiniApp(Profile* profile, const std::string& app_id) { ...@@ -89,7 +93,7 @@ void LaunchCrostiniApp(Profile* profile, const std::string& app_id) {
RecordAppLaunchHistogram(CrostiniAppLaunchAppType::kTerminal); RecordAppLaunchHistogram(CrostiniAppLaunchAppType::kTerminal);
if (!crostini_manager->IsCrosTerminaInstalled() || if (!crostini_manager->IsCrosTerminaInstalled() ||
!profile->GetPrefs()->GetBoolean(crostini::prefs::kCrostiniEnabled)) { !IsCrostiniEnabled(profile)) {
CrostiniInstallerView::Show(profile); CrostiniInstallerView::Show(profile);
} else { } else {
crostini_manager->RestartCrostini( crostini_manager->RestartCrostini(
......
...@@ -17,6 +17,10 @@ bool IsCrostiniAllowed(); ...@@ -17,6 +17,10 @@ bool IsCrostiniAllowed();
// Returns true if crostini UI can be shown. Implies crostini is allowed to run. // Returns true if crostini UI can be shown. Implies crostini is allowed to run.
bool IsCrostiniUIAllowedForProfile(Profile* profile); bool IsCrostiniUIAllowedForProfile(Profile* profile);
// Returns whether if Crostini has been enabled, i.e. the user has launched it
// at least once and not deleted it.
bool IsCrostiniEnabled(Profile* profile);
// |app_id| should be a valid Crostini app list id. // |app_id| should be a valid Crostini app list id.
void LaunchCrostiniApp(Profile* profile, const std::string& app_id); void LaunchCrostiniApp(Profile* profile, const std::string& app_id);
......
...@@ -3635,6 +3635,8 @@ split_static_library("ui") { ...@@ -3635,6 +3635,8 @@ split_static_library("ui") {
"app_list/internal_app/internal_app_model_builder.h", "app_list/internal_app/internal_app_model_builder.h",
"app_list/search/arc_app_result.cc", "app_list/search/arc_app_result.cc",
"app_list/search/arc_app_result.h", "app_list/search/arc_app_result.h",
"app_list/search/crostini_app_result.cc",
"app_list/search/crostini_app_result.h",
"app_list/search/internal_app_result.cc", "app_list/search/internal_app_result.cc",
"app_list/search/internal_app_result.h", "app_list/search/internal_app_result.h",
"ash/launcher/app_window_base.cc", "ash/launcher/app_window_base.cc",
......
...@@ -5,12 +5,16 @@ ...@@ -5,12 +5,16 @@
#include "chrome/browser/ui/app_list/crostini/crostini_app_model_builder.h" #include "chrome/browser/ui/app_list/crostini/crostini_app_model_builder.h"
#include "ash/resources/grit/ash_resources.h" #include "ash/resources/grit/ash_resources.h"
#include "chrome/browser/chromeos/crostini/crostini_manager.h"
#include "chrome/browser/chromeos/crostini/crostini_pref_names.h"
#include "chrome/browser/chromeos/crostini/crostini_registry_service_factory.h" #include "chrome/browser/chromeos/crostini/crostini_registry_service_factory.h"
#include "chrome/browser/chromeos/crostini/crostini_util.h" #include "chrome/browser/chromeos/crostini/crostini_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/app_list/app_list_controller_delegate.h" #include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
#include "chrome/browser/ui/app_list/crostini/crostini_app_item.h" #include "chrome/browser/ui/app_list/crostini/crostini_app_item.h"
#include "chrome/grit/chrome_unscaled_resources.h" #include "chrome/grit/chrome_unscaled_resources.h"
#include "components/crx_file/id_util.h" #include "components/crx_file/id_util.h"
#include "components/prefs/pref_change_registrar.h"
#include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h" #include "ui/base/resource/resource_bundle.h"
...@@ -31,11 +35,23 @@ void CrostiniAppModelBuilder::BuildModel() { ...@@ -31,11 +35,23 @@ void CrostiniAppModelBuilder::BuildModel() {
} }
registry_service->AddObserver(this); registry_service->AddObserver(this);
pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
pref_change_registrar_->Init(profile()->GetPrefs());
pref_change_registrar_->Add(
crostini::prefs::kCrostiniEnabled,
base::BindRepeating(&CrostiniAppModelBuilder::OnCrostiniEnabledChanged,
base::Unretained(this)));
} }
void CrostiniAppModelBuilder::InsertCrostiniAppItem( void CrostiniAppModelBuilder::InsertCrostiniAppItem(
const crostini::CrostiniRegistryService* registry_service, const crostini::CrostiniRegistryService* registry_service,
const std::string& app_id) { const std::string& app_id) {
if (app_id == kCrostiniTerminalId && !IsCrostiniEnabled(profile())) {
// If Crostini isn't enabled, don't show the Terminal item until it
// becomes enabled.
return;
}
std::unique_ptr<crostini::CrostiniRegistryService::Registration> std::unique_ptr<crostini::CrostiniRegistryService::Registration>
registration = registry_service->GetRegistration(app_id); registration = registry_service->GetRegistration(app_id);
DCHECK(registration); DCHECK(registration);
...@@ -76,3 +92,14 @@ void CrostiniAppModelBuilder::OnAppIconUpdated(const std::string& app_id, ...@@ -76,3 +92,14 @@ void CrostiniAppModelBuilder::OnAppIconUpdated(const std::string& app_id,
// Initiate async icon reloading. // Initiate async icon reloading.
app_item->crostini_app_icon()->LoadForScaleFactor(scale_factor); app_item->crostini_app_icon()->LoadForScaleFactor(scale_factor);
} }
void CrostiniAppModelBuilder::OnCrostiniEnabledChanged() {
if (IsCrostiniEnabled(profile())) {
crostini::CrostiniRegistryService* registry_service =
crostini::CrostiniRegistryServiceFactory::GetForProfile(profile());
InsertCrostiniAppItem(registry_service, kCrostiniTerminalId);
} else {
const bool unsynced_change = false;
RemoveApp(kCrostiniTerminalId, unsynced_change);
}
}
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "chrome/browser/ui/app_list/app_list_model_builder.h" #include "chrome/browser/ui/app_list/app_list_model_builder.h"
class AppListControllerDelegate; class AppListControllerDelegate;
class PrefChangeRegistrar;
// This class populates and maintains Crostini apps. // This class populates and maintains Crostini apps.
class CrostiniAppModelBuilder class CrostiniAppModelBuilder
...@@ -35,6 +36,11 @@ class CrostiniAppModelBuilder ...@@ -35,6 +36,11 @@ class CrostiniAppModelBuilder
const crostini::CrostiniRegistryService* registry_service, const crostini::CrostiniRegistryService* registry_service,
const std::string& app_id); const std::string& app_id);
void OnCrostiniEnabledChanged();
// Observer Crostini installation so we can start showing The Terminal app.
std::unique_ptr<PrefChangeRegistrar> pref_change_registrar_;
DISALLOW_COPY_AND_ASSIGN(CrostiniAppModelBuilder); DISALLOW_COPY_AND_ASSIGN(CrostiniAppModelBuilder);
}; };
......
...@@ -6,15 +6,18 @@ ...@@ -6,15 +6,18 @@
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
#include "chrome/browser/chromeos/crostini/crostini_pref_names.h"
#include "chrome/browser/chromeos/crostini/crostini_util.h" #include "chrome/browser/chromeos/crostini/crostini_util.h"
#include "chrome/browser/ui/app_list/app_list_client_impl.h" #include "chrome/browser/ui/app_list/app_list_client_impl.h"
#include "chrome/browser/ui/app_list/app_list_service_impl.h" #include "chrome/browser/ui/app_list/app_list_service_impl.h"
#include "chrome/browser/ui/app_list/crostini/crostini_app_model_builder.h" #include "chrome/browser/ui/app_list/crostini/crostini_app_model_builder.h"
#include "chrome/browser/ui/app_list/test/chrome_app_list_test_support.h" #include "chrome/browser/ui/app_list/test/chrome_app_list_test_support.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/test/test_browser_dialog.h" #include "chrome/browser/ui/test/test_browser_dialog.h"
#include "chrome/common/chrome_features.h" #include "chrome/common/chrome_features.h"
#include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/in_process_browser_test.h"
#include "components/crx_file/id_util.h" #include "components/crx_file/id_util.h"
#include "components/prefs/pref_service.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "ui/views/window/dialog_client_view.h" #include "ui/views/window/dialog_client_view.h"
...@@ -35,6 +38,11 @@ class CrostiniInstallerViewBrowserTest : public DialogBrowserTest { ...@@ -35,6 +38,11 @@ class CrostiniInstallerViewBrowserTest : public DialogBrowserTest {
DialogBrowserTest::SetUp(); DialogBrowserTest::SetUp();
} }
void SetUpOnMainThread() override {
browser()->profile()->GetPrefs()->SetBoolean(
crostini::prefs::kCrostiniEnabled, true);
}
CrostiniInstallerView* ActiveView() { CrostiniInstallerView* ActiveView() {
return CrostiniInstallerView::GetActiveViewForTesting(); return CrostiniInstallerView::GetActiveViewForTesting();
} }
......
...@@ -23,6 +23,10 @@ ...@@ -23,6 +23,10 @@
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h"
#include "base/time/clock.h" #include "base/time/clock.h"
#include "chrome/browser/chromeos/arc/arc_util.h" #include "chrome/browser/chromeos/arc/arc_util.h"
#include "chrome/browser/chromeos/crostini/crostini_manager.h"
#include "chrome/browser/chromeos/crostini/crostini_registry_service.h"
#include "chrome/browser/chromeos/crostini/crostini_registry_service_factory.h"
#include "chrome/browser/chromeos/crostini/crostini_util.h"
#include "chrome/browser/chromeos/extensions/gfx_utils.h" #include "chrome/browser/chromeos/extensions/gfx_utils.h"
#include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_ui_util.h" #include "chrome/browser/extensions/extension_ui_util.h"
...@@ -34,6 +38,7 @@ ...@@ -34,6 +38,7 @@
#include "chrome/browser/ui/app_list/chrome_app_list_item.h" #include "chrome/browser/ui/app_list/chrome_app_list_item.h"
#include "chrome/browser/ui/app_list/internal_app/internal_app_metadata.h" #include "chrome/browser/ui/app_list/internal_app/internal_app_metadata.h"
#include "chrome/browser/ui/app_list/search/arc_app_result.h" #include "chrome/browser/ui/app_list/search/arc_app_result.h"
#include "chrome/browser/ui/app_list/search/crostini_app_result.h"
#include "chrome/browser/ui/app_list/search/extension_app_result.h" #include "chrome/browser/ui/app_list/search/extension_app_result.h"
#include "chrome/browser/ui/app_list/search/internal_app_result.h" #include "chrome/browser/ui/app_list/search/internal_app_result.h"
#include "extensions/browser/extension_prefs.h" #include "extensions/browser/extension_prefs.h"
...@@ -148,6 +153,11 @@ class AppSearchProvider::App { ...@@ -148,6 +153,11 @@ class AppSearchProvider::App {
searchable_text_ = searchable_text; searchable_text_ = searchable_text;
} }
bool require_exact_match() const { return require_exact_match_; }
void set_require_exact_match(bool require_exact_match) {
require_exact_match_ = require_exact_match;
}
private: private:
AppSearchProvider::DataSource* data_source_; AppSearchProvider::DataSource* data_source_;
std::unique_ptr<TokenizedString> tokenized_indexed_name_; std::unique_ptr<TokenizedString> tokenized_indexed_name_;
...@@ -158,6 +168,7 @@ class AppSearchProvider::App { ...@@ -158,6 +168,7 @@ class AppSearchProvider::App {
const base::Time install_time_; const base::Time install_time_;
bool recommendable_ = true; bool recommendable_ = true;
base::string16 searchable_text_; base::string16 searchable_text_;
bool require_exact_match_ = false;
DISALLOW_COPY_AND_ASSIGN(App); DISALLOW_COPY_AND_ASSIGN(App);
}; };
...@@ -357,6 +368,68 @@ class InternalDataSource : public AppSearchProvider::DataSource { ...@@ -357,6 +368,68 @@ class InternalDataSource : public AppSearchProvider::DataSource {
DISALLOW_COPY_AND_ASSIGN(InternalDataSource); DISALLOW_COPY_AND_ASSIGN(InternalDataSource);
}; };
class CrostiniDataSource : public AppSearchProvider::DataSource,
public crostini::CrostiniRegistryService::Observer {
public:
CrostiniDataSource(Profile* profile, AppSearchProvider* owner)
: AppSearchProvider::DataSource(profile, owner) {
crostini::CrostiniRegistryServiceFactory::GetForProfile(profile)
->AddObserver(this);
}
~CrostiniDataSource() override {
crostini::CrostiniRegistryServiceFactory::GetForProfile(profile())
->RemoveObserver(this);
}
// AppSearchProvider::DataSource overrides:
void AddApps(AppSearchProvider::Apps* apps) override {
crostini::CrostiniRegistryService* registry_service =
crostini::CrostiniRegistryServiceFactory::GetForProfile(profile());
for (const std::string& app_id : registry_service->GetRegisteredAppIds()) {
std::unique_ptr<crostini::CrostiniRegistryService::Registration>
registration = registry_service->GetRegistration(app_id);
if (registration->no_display)
continue;
const std::string& name =
crostini::CrostiniRegistryService::Registration::Localize(
registration->name);
// Eventually it would be nice to use additional data points, for example
// the 'Keywords' desktop entry field and the executable file name.
apps->emplace_back(std::make_unique<AppSearchProvider::App>(
this, app_id, name, registration->last_launch_time,
registration->install_time));
// Until it's been installed, the Terminal is hidden unless you search
// for 'Terminal' exactly (case insensitive).
if (app_id == kCrostiniTerminalId && !IsCrostiniEnabled(profile())) {
apps->back()->set_recommendable(false);
apps->back()->set_require_exact_match(true);
}
}
}
std::unique_ptr<AppResult> CreateResult(
const std::string& app_id,
AppListControllerDelegate* list_controller,
bool is_recommended) override {
return std::make_unique<CrostiniAppResult>(profile(), app_id,
list_controller, is_recommended);
}
// crostini::CrostiniRegistryService::Observer overrides:
void OnRegistryUpdated(
crostini::CrostiniRegistryService* registry_service,
const std::vector<std::string>& updated_apps,
const std::vector<std::string>& removed_apps,
const std::vector<std::string>& inserted_apps) override {
owner()->RefreshAppsAndUpdateResults(!removed_apps.empty());
}
private:
DISALLOW_COPY_AND_ASSIGN(CrostiniDataSource);
};
} // namespace } // namespace
AppSearchProvider::AppSearchProvider(Profile* profile, AppSearchProvider::AppSearchProvider(Profile* profile,
...@@ -371,6 +444,10 @@ AppSearchProvider::AppSearchProvider(Profile* profile, ...@@ -371,6 +444,10 @@ AppSearchProvider::AppSearchProvider(Profile* profile,
std::make_unique<ExtensionDataSource>(profile, this)); std::make_unique<ExtensionDataSource>(profile, this));
if (arc::IsArcAllowedForProfile(profile)) if (arc::IsArcAllowedForProfile(profile))
data_sources_.emplace_back(std::make_unique<ArcDataSource>(profile, this)); data_sources_.emplace_back(std::make_unique<ArcDataSource>(profile, this));
if (IsCrostiniUIAllowedForProfile(profile)) {
data_sources_.emplace_back(
std::make_unique<CrostiniDataSource>(profile, this));
}
data_sources_.emplace_back( data_sources_.emplace_back(
std::make_unique<InternalDataSource>(profile, this)); std::make_unique<InternalDataSource>(profile, this));
} }
...@@ -443,8 +520,11 @@ void AppSearchProvider::UpdateQueriedResults() { ...@@ -443,8 +520,11 @@ void AppSearchProvider::UpdateQueriedResults() {
const TokenizedString query_terms(query_); const TokenizedString query_terms(query_);
for (auto& app : apps_) { for (auto& app : apps_) {
std::unique_ptr<AppResult> result = if (app->require_exact_match() &&
app->data_source()->CreateResult(app->id(), list_controller_, false); !base::EqualsCaseInsensitiveASCII(query_, app->name())) {
continue;
}
TokenizedStringMatch match; TokenizedStringMatch match;
TokenizedString* indexed_name = app->GetTokenizedIndexedName(); TokenizedString* indexed_name = app->GetTokenizedIndexedName();
if (!match.Calculate(query_terms, *indexed_name) && if (!match.Calculate(query_terms, *indexed_name) &&
...@@ -452,6 +532,8 @@ void AppSearchProvider::UpdateQueriedResults() { ...@@ -452,6 +532,8 @@ void AppSearchProvider::UpdateQueriedResults() {
continue; continue;
} }
std::unique_ptr<AppResult> result =
app->data_source()->CreateResult(app->id(), list_controller_, false);
result->UpdateFromMatch(*indexed_name, match); result->UpdateFromMatch(*indexed_name, match);
MaybeAddResult(&new_results, std::move(result), &seen_or_filtered_apps); MaybeAddResult(&new_results, std::move(result), &seen_or_filtered_apps);
} }
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/app_list/search/crostini_app_result.h"
#include "chrome/browser/chromeos/crostini/crostini_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/app_list/crostini/crostini_app_context_menu.h"
#include "chrome/browser/ui/app_list/crostini/crostini_app_icon_loader.h"
#include "ui/app_list/app_list_constants.h"
namespace app_list {
CrostiniAppResult::CrostiniAppResult(Profile* profile,
const std::string& app_id,
AppListControllerDelegate* controller,
bool is_recommendation)
: AppResult(profile, app_id, controller, is_recommendation) {
set_id(app_id);
icon_loader_.reset(new CrostiniAppIconLoader(
profile, GetPreferredIconDimension(display_type()), this));
icon_loader_->FetchImage(app_id);
}
CrostiniAppResult::~CrostiniAppResult() = default;
void CrostiniAppResult::Open(int event_flags) {
LaunchCrostiniApp(profile(), app_id());
}
std::unique_ptr<ChromeSearchResult> CrostiniAppResult::Duplicate() const {
auto copy = std::make_unique<CrostiniAppResult>(
profile(), app_id(), controller(),
display_type() == ash::SearchResultDisplayType::kRecommendation);
copy->set_title(title());
copy->set_title_tags(title_tags());
copy->set_relevance(relevance());
return copy;
}
void CrostiniAppResult::GetContextMenuModel(GetMenuModelCallback callback) {
context_menu_ = std::make_unique<CrostiniAppContextMenu>(profile(), app_id(),
controller());
context_menu_->GetMenuModel(std::move(callback));
}
void CrostiniAppResult::ExecuteLaunchCommand(int event_flags) {
Open(event_flags);
}
void CrostiniAppResult::OnAppImageUpdated(const std::string& app_id,
const gfx::ImageSkia& image) {
SetIcon(image);
}
} // namespace app_list
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_APP_LIST_SEARCH_CROSTINI_APP_RESULT_H_
#define CHROME_BROWSER_UI_APP_LIST_SEARCH_CROSTINI_APP_RESULT_H_
#include <memory>
#include "base/macros.h"
#include "chrome/browser/ui/app_icon_loader_delegate.h"
#include "chrome/browser/ui/app_list/search/app_result.h"
class CrostiniAppContextMenu;
class CrostiniAppIconLoader;
namespace app_list {
// Result of CrostiniSearchProvider.
class CrostiniAppResult : public AppResult, public AppIconLoaderDelegate {
public:
CrostiniAppResult(Profile* profile,
const std::string& app_id,
AppListControllerDelegate* controller,
bool is_recommendation);
~CrostiniAppResult() override;
// AppResult overrides:
void Open(int event_flags) override;
std::unique_ptr<ChromeSearchResult> Duplicate() const override;
void GetContextMenuModel(GetMenuModelCallback callback) override;
void ExecuteLaunchCommand(int event_flags) override;
// AppIconLoaderDelegate overrides:
void OnAppImageUpdated(const std::string& app_id,
const gfx::ImageSkia& image) override;
private:
std::unique_ptr<CrostiniAppIconLoader> icon_loader_;
std::unique_ptr<CrostiniAppContextMenu> context_menu_;
DISALLOW_COPY_AND_ASSIGN(CrostiniAppResult);
};
} // namespace app_list
#endif // CHROME_BROWSER_UI_APP_LIST_SEARCH_CROSTINI_APP_RESULT_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment