| // Copyright 2024 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/picker/picker_client_impl.h" |
| |
| #include <cstdint> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #include "ash/constants/ash_features.h" |
| #include "ash/picker/picker_controller.h" |
| #include "ash/public/cpp/picker/picker_search_result.h" |
| #include "ash/public/cpp/picker/picker_web_paste_target.h" |
| #include "base/check.h" |
| #include "base/check_deref.h" |
| #include "base/containers/span.h" |
| #include "base/files/file_enumerator.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/logging.h" |
| #include "base/notimplemented.h" |
| #include "base/ranges/algorithm.h" |
| #include "base/ranges/functional.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/ash/app_list/app_list_controller_delegate.h" |
| #include "chrome/browser/ash/app_list/search/chrome_search_result.h" |
| #include "chrome/browser/ash/app_list/search/files/drive_search_provider.h" |
| #include "chrome/browser/ash/app_list/search/files/file_search_provider.h" |
| #include "chrome/browser/ash/app_list/search/omnibox/omnibox_lacros_provider.h" |
| #include "chrome/browser/ash/app_list/search/omnibox/omnibox_provider.h" |
| #include "chrome/browser/ash/app_list/search/ranking/ranker_manager.h" |
| #include "chrome/browser/ash/app_list/search/search_engine.h" |
| #include "chrome/browser/ash/app_list/search/types.h" |
| #include "chrome/browser/ash/crosapi/browser_util.h" |
| #include "chrome/browser/ash/file_manager/fileapi_util.h" |
| #include "chrome/browser/ash/input_method/editor_mediator_factory.h" |
| #include "chrome/browser/chromeos/launcher_search/search_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/ash/picker/picker_file_suggester.h" |
| #include "chrome/browser/ui/ash/picker/picker_lacros_omnibox_search_provider.h" |
| #include "chrome/browser/ui/ash/picker/picker_link_suggester.h" |
| #include "chrome/browser/ui/ash/picker/picker_thumbnail_loader.h" |
| #include "chromeos/ash/components/browser_context_helper/browser_context_helper.h" |
| #include "chromeos/ash/components/drivefs/mojom/drivefs.mojom.h" |
| #include "chromeos/components/editor_menu/public/cpp/preset_text_query.h" |
| #include "chromeos/constants/chromeos_features.h" |
| #include "components/user_manager/user.h" |
| #include "components/user_manager/user_manager.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/render_widget_host.h" |
| #include "content/public/browser/render_widget_host_iterator.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/browser/web_contents.h" |
| #include "google_apis/gaia/gaia_auth_util.h" |
| #include "ui/aura/window.h" |
| #include "ui/base/page_transition_types.h" |
| #include "ui/base/window_open_disposition.h" |
| #include "ui/gfx/native_widget_types.h" |
| #include "url/gurl.h" |
| |
| namespace ash { |
| enum class AppListSearchResultType; |
| } |
| |
| namespace { |
| |
| // TODO: b/345303965 - Finalize this string. |
| constexpr std::u16string_view kAnnouncementViewName = u"Picker"; |
| |
| bool IsSupportedLocalFileFormat(const base::FilePath& file_path) { |
| for (std::string_view extension : |
| {".jpg", ".jpeg", ".png", ".gif", ".webp"}) { |
| if (file_path.MatchesFinalExtension(extension)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| std::vector<ash::PickerSearchResult> CreateSearchResultsForRecentLocalImages( |
| std::vector<PickerFileSuggester::LocalFile> files) { |
| std::vector<ash::PickerSearchResult> results; |
| results.reserve(files.size()); |
| for (PickerFileSuggester::LocalFile& file : files) { |
| results.push_back(ash::PickerLocalFileResult(std::move(file.title), |
| std::move(file.path))); |
| } |
| return results; |
| } |
| |
| std::vector<ash::PickerSearchResult> CreateSearchResultsForRecentDriveFiles( |
| std::vector<PickerFileSuggester::DriveFile> files) { |
| std::vector<ash::PickerSearchResult> results; |
| results.reserve(files.size()); |
| for (PickerFileSuggester::DriveFile& file : files) { |
| results.push_back( |
| ash::PickerDriveFileResult(std::move(file.id), std::move(file.title), |
| std::move(file.url), file.local_path)); |
| } |
| return results; |
| } |
| |
| std::unique_ptr<app_list::SearchProvider> CreateDriveSearchProvider( |
| Profile* profile) { |
| auto provider = std::make_unique<app_list::DriveSearchProvider>( |
| profile, /*should_filter_shared_files=*/false, |
| /*should_filter_directories=*/true); |
| if (base::FeatureList::IsEnabled(ash::features::kPickerCloud)) { |
| provider->SetQuerySource( |
| drivefs::mojom::QueryParameters::QuerySource::kCloudOnly); |
| } |
| return provider; |
| } |
| |
| std::unique_ptr<app_list::SearchProvider> CreateFileSearchProvider( |
| Profile* profile) { |
| return std::make_unique<app_list::FileSearchProvider>( |
| profile, base::FileEnumerator::FileType::FILES); |
| } |
| |
| std::vector<ash::PickerSearchResult> ConvertSearchResults( |
| std::vector<std::unique_ptr<ChromeSearchResult>> results) { |
| std::vector<ash::PickerSearchResult> picker_results; |
| picker_results.reserve(results.size()); |
| |
| for (const std::unique_ptr<ChromeSearchResult>& result : results) { |
| CHECK(result); |
| } |
| |
| base::ranges::sort(results, base::ranges::greater(), |
| [](const std::unique_ptr<ChromeSearchResult>& result) { |
| return result->relevance(); |
| }); |
| |
| for (const std::unique_ptr<ChromeSearchResult>& result : results) { |
| switch (result->result_type()) { |
| case ash::AppListSearchResultType::kOmnibox: |
| case ash::AppListSearchResultType::kOpenTab: { |
| if (result->metrics_type() == ash::OMNIBOX_URL_WHAT_YOU_TYPED) { |
| continue; |
| } |
| |
| if (std::optional<GURL> result_url = result->url(); |
| result_url.has_value()) { |
| picker_results.push_back(ash::PickerBrowsingHistoryResult( |
| *result_url, result->title(), result->icon().icon, |
| result->best_match())); |
| } else { |
| picker_results.push_back(ash::PickerTextResult( |
| result->title(), ash::PickerTextResult::Source::kOmnibox)); |
| } |
| break; |
| } |
| case ash::AppListSearchResultType::kFileSearch: { |
| // TODO: b/322926411 - Move this filtering to the search provider. |
| if (IsSupportedLocalFileFormat(result->filePath())) { |
| picker_results.push_back(ash::PickerLocalFileResult( |
| result->title(), result->filePath(), result->best_match())); |
| } |
| break; |
| } |
| case ash::AppListSearchResultType::kDriveSearch: |
| picker_results.push_back(ash::PickerDriveFileResult( |
| result->DriveId(), result->title(), *result->url(), |
| result->filePath(), result->best_match())); |
| break; |
| default: |
| LOG(DFATAL) << "Got unexpected search result type " |
| << static_cast<int>(result->result_type()); |
| break; |
| } |
| } |
| |
| return picker_results; |
| } |
| |
| ash::input_method::EditorMediator* GetEditorMediator(Profile* profile) { |
| if (!chromeos::features::IsOrcaEnabled()) { |
| return nullptr; |
| } |
| |
| return ash::input_method::EditorMediatorFactory::GetInstance()->GetForProfile( |
| profile); |
| } |
| |
| // TODO: b/326847990 - Remove this once it's moved to mojom traits. |
| chromeos::editor_menu::PresetQueryCategory FromMojoPresetQueryCategory( |
| const crosapi::mojom::EditorPanelPresetQueryCategory category) { |
| using EditorPanelPresetQueryCategory = |
| crosapi::mojom::EditorPanelPresetQueryCategory; |
| using PresetQueryCategory = chromeos::editor_menu::PresetQueryCategory; |
| |
| switch (category) { |
| case EditorPanelPresetQueryCategory::kUnknown: |
| return PresetQueryCategory::kUnknown; |
| case EditorPanelPresetQueryCategory::kShorten: |
| return PresetQueryCategory::kShorten; |
| case EditorPanelPresetQueryCategory::kElaborate: |
| return PresetQueryCategory::kElaborate; |
| case EditorPanelPresetQueryCategory::kRephrase: |
| return PresetQueryCategory::kRephrase; |
| case EditorPanelPresetQueryCategory::kFormalize: |
| return PresetQueryCategory::kFormalize; |
| case EditorPanelPresetQueryCategory::kEmojify: |
| return PresetQueryCategory::kEmojify; |
| case EditorPanelPresetQueryCategory::kProofread: |
| return PresetQueryCategory::kProofread; |
| } |
| } |
| |
| std::vector<ash::PickerSearchResult> GetEditorResultsFromPanelContext( |
| crosapi::mojom::EditorPanelContextPtr panel_context) { |
| std::vector<ash::PickerSearchResult> results; |
| for (const crosapi::mojom::EditorPanelPresetTextQueryPtr& query : |
| panel_context->preset_text_queries) { |
| results.push_back(ash::PickerEditorResult( |
| ash::PickerEditorResult::Mode::kRewrite, base::UTF8ToUTF16(query->name), |
| FromMojoPresetQueryCategory(query->category), query->text_query_id)); |
| } |
| return results; |
| } |
| |
| app_list::CategoriesList CreateRankerCategories() { |
| app_list::CategoriesList res({{.category = app_list::Category::kWeb}, |
| {.category = app_list::Category::kFiles}}); |
| return res; |
| } |
| |
| } // namespace |
| |
| PickerClientImpl::PickerClientImpl(ash::PickerController* controller, |
| user_manager::UserManager* user_manager) |
| : announcer_(kAnnouncementViewName), controller_(controller) { |
| controller_->SetClient(this); |
| |
| // As `PickerClientImpl` is initialised in |
| // `ChromeBrowserMainExtraPartsAsh::PostProfileInit`, the user manager does |
| // not notify us of the first user "change". |
| ActiveUserChanged(user_manager->GetActiveUser()); |
| user_session_state_observation_.Observe(user_manager); |
| } |
| |
| PickerClientImpl::~PickerClientImpl() { |
| // Calling `PickerController::SetClient` with null requires the old client |
| // (this client) to be valid. This is fine as we have not started destructing |
| // anything yet. |
| controller_->SetClient(nullptr); |
| } |
| |
| void PickerClientImpl::StartCrosSearch( |
| const std::u16string& query, |
| std::optional<ash::PickerCategory> category, |
| CrosSearchResultsCallback callback) { |
| ranker_categories_ = CreateRankerCategories(); |
| ranker_manager_->Start(query, ranker_categories_); |
| if (!category.has_value()) { |
| CHECK(search_engine_); |
| search_engine_->StartSearch( |
| query, app_list::SearchOptions(), |
| base::BindRepeating(&PickerClientImpl::OnCrosSearchResultsUpdated, |
| weak_factory_.GetWeakPtr(), std::move(callback))); |
| return; |
| } |
| |
| switch (*category) { |
| case ash::PickerCategory::kEditorWrite: |
| case ash::PickerCategory::kEditorRewrite: |
| case ash::PickerCategory::kEmojisGifs: |
| case ash::PickerCategory::kEmojis: |
| case ash::PickerCategory::kClipboard: |
| case ash::PickerCategory::kDatesTimes: |
| case ash::PickerCategory::kUnitsMaths: |
| DLOG(FATAL) << "Unexpected category for StartCrosSearch: " |
| << static_cast<int>(*category); |
| break; |
| case ash::PickerCategory::kLinks: |
| case ash::PickerCategory::kDriveFiles: |
| case ash::PickerCategory::kLocalFiles: { |
| if (filtered_search_engine_ == nullptr || |
| current_filter_category_ != category) { |
| filtered_search_engine_ = |
| std::make_unique<app_list::SearchEngine>(profile_); |
| filtered_search_engine_->AddProvider( |
| CreateSearchProviderForCategory(*category)); |
| current_filter_category_ = category; |
| } |
| |
| filtered_search_engine_->StartSearch( |
| query, app_list::SearchOptions(), |
| base::BindRepeating(&PickerClientImpl::OnCrosSearchResultsUpdated, |
| weak_factory_.GetWeakPtr(), std::move(callback))); |
| } break; |
| } |
| } |
| |
| void PickerClientImpl::OnCrosSearchResultsUpdated( |
| PickerClientImpl::CrosSearchResultsCallback callback, |
| ash::AppListSearchResultType result_type, |
| std::vector<std::unique_ptr<ChromeSearchResult>> results) { |
| app_list::ResultsMap results_map; |
| results_map[result_type] = std::move(results); |
| if (ranker_manager_ != nullptr) { |
| ranker_manager_->UpdateResultRanks(results_map, result_type); |
| } |
| callback.Run(result_type, |
| ConvertSearchResults(std::move(results_map[result_type]))); |
| } |
| |
| void PickerClientImpl::StopCrosQuery() { |
| CHECK(search_engine_); |
| search_engine_->StopQuery(); |
| } |
| |
| bool PickerClientImpl::IsEligibleForEditor() { |
| ash::input_method::EditorMediator* editor_mediator = |
| GetEditorMediator(profile_); |
| if (editor_mediator == nullptr) { |
| return false; |
| } |
| |
| return editor_mediator->GetEditorMode() != |
| ash::input_method::EditorMode::kHardBlocked; |
| } |
| |
| PickerClientImpl::ShowEditorCallback PickerClientImpl::CacheEditorContext() { |
| ash::input_method::EditorMediator* editor_mediator = |
| GetEditorMediator(profile_); |
| if (editor_mediator == nullptr) { |
| return {}; |
| } |
| |
| editor_mediator->CacheContext(); |
| |
| ash::input_method::EditorMode editor_mode = editor_mediator->GetEditorMode(); |
| if (editor_mode == ash::input_method::EditorMode::kSoftBlocked || |
| editor_mode == ash::input_method::EditorMode::kHardBlocked) { |
| return {}; |
| } |
| |
| return base::BindOnce(&PickerClientImpl::ShowEditor, |
| weak_factory_.GetWeakPtr()); |
| } |
| |
| void PickerClientImpl::GetSuggestedEditorResults( |
| SuggestedEditorResultsCallback callback) { |
| ash::input_method::EditorMediator* editor_mediator = |
| GetEditorMediator(profile_); |
| if (editor_mediator == nullptr || |
| editor_mediator->panel_manager() == nullptr) { |
| std::move(callback).Run({}); |
| return; |
| } |
| |
| ash::input_method::EditorMode editor_mode = editor_mediator->GetEditorMode(); |
| if (editor_mode == ash::input_method::EditorMode::kHardBlocked || |
| editor_mode == ash::input_method::EditorMode::kSoftBlocked) { |
| std::move(callback).Run({}); |
| return; |
| } |
| |
| editor_mediator->panel_manager()->GetEditorPanelContext( |
| base::BindOnce(GetEditorResultsFromPanelContext) |
| .Then(std::move(callback))); |
| } |
| |
| void PickerClientImpl::GetRecentLocalFileResults(size_t max_files, |
| RecentFilesCallback callback) { |
| file_suggester_->GetRecentLocalImages( |
| max_files, base::BindOnce(CreateSearchResultsForRecentLocalImages) |
| .Then(std::move(callback))); |
| } |
| |
| void PickerClientImpl::GetRecentDriveFileResults(size_t max_files, |
| RecentFilesCallback callback) { |
| file_suggester_->GetRecentDriveFiles( |
| max_files, base::BindOnce(CreateSearchResultsForRecentDriveFiles) |
| .Then(std::move(callback))); |
| } |
| |
| void PickerClientImpl::GetSuggestedLinkResults( |
| size_t max_results, |
| SuggestedLinksCallback callback) { |
| link_suggester_->GetSuggestedLinks(max_results, std::move(callback)); |
| } |
| |
| bool PickerClientImpl::IsFeatureAllowedForDogfood() { |
| return gaia::IsGoogleInternalAccountEmail(profile_->GetProfileUserName()); |
| } |
| |
| void PickerClientImpl::FetchFileThumbnail(const base::FilePath& path, |
| const gfx::Size& size, |
| FetchFileThumbnailCallback callback) { |
| CHECK(thumbnail_loader_); |
| thumbnail_loader_->Load(path, size, std::move(callback)); |
| } |
| |
| PrefService* PickerClientImpl::GetPrefs() { |
| return profile_ == nullptr ? nullptr : profile_->GetPrefs(); |
| } |
| |
| // Forked from `ClipboardHistoryControllerDelegateImpl::Paste`. |
| std::optional<ash::PickerWebPasteTarget> PickerClientImpl::GetWebPasteTarget() { |
| std::unique_ptr<content::RenderWidgetHostIterator> widgets = |
| content::RenderWidgetHost::GetRenderWidgetHosts(); |
| while (content::RenderWidgetHost* rwh = widgets->GetNextHost()) { |
| content::RenderViewHost* rvh = content::RenderViewHost::From(rwh); |
| if (rvh == nullptr) { |
| continue; |
| } |
| |
| content::WebContents* web_contents = |
| content::WebContents::FromRenderViewHost(rvh); |
| if (web_contents == nullptr) { |
| continue; |
| } |
| if (web_contents->GetPrimaryMainFrame()->GetRenderViewHost() != rvh) { |
| continue; |
| } |
| |
| content::RenderFrameHost* focused_frame = web_contents->GetFocusedFrame(); |
| if (focused_frame == nullptr) { |
| continue; |
| } |
| |
| content::WebContents* focused_web_contents = |
| content::WebContents::FromRenderFrameHost(focused_frame); |
| if (focused_web_contents == nullptr) { |
| continue; |
| } |
| |
| gfx::NativeView window = focused_web_contents->GetContentNativeView(); |
| if (window == nullptr) { |
| continue; |
| } |
| if (!window->HasFocus()) { |
| continue; |
| } |
| |
| return std::make_optional<ash::PickerWebPasteTarget>( |
| focused_web_contents->GetLastCommittedURL(), |
| // SAFETY: Callers must call this synchronously as per the |
| // documentation, so this `base::Unretained` is safe. |
| base::BindOnce(&content::WebContents::Paste, |
| base::Unretained(focused_web_contents))); |
| } |
| |
| return std::nullopt; |
| } |
| |
| void PickerClientImpl::Announce(std::u16string_view message) { |
| announcer_.Announce(std::u16string(message)); |
| } |
| |
| void PickerClientImpl::ActiveUserChanged(user_manager::User* active_user) { |
| if (!active_user) { |
| SetProfile(nullptr); |
| return; |
| } |
| |
| active_user->AddProfileCreatedObserver( |
| base::BindOnce(&PickerClientImpl::SetProfileByUser, |
| weak_factory_.GetWeakPtr(), active_user)); |
| } |
| |
| void PickerClientImpl::SetProfileByUser(const user_manager::User* user) { |
| Profile* profile = Profile::FromBrowserContext( |
| ash::BrowserContextHelper::Get()->GetBrowserContextByUser(user)); |
| SetProfile(profile); |
| } |
| |
| void PickerClientImpl::SetProfile(Profile* profile) { |
| if (profile_ == profile) { |
| return; |
| } |
| |
| profile_ = profile; |
| |
| search_engine_ = std::make_unique<app_list::SearchEngine>(profile_); |
| search_engine_->AddProvider(CreateOmniboxProvider( |
| /*bookmarks=*/true, /*history=*/true, /*open_tabs=*/true)); |
| search_engine_->AddProvider(CreateFileSearchProvider(profile_)); |
| search_engine_->AddProvider(CreateDriveSearchProvider(profile_)); |
| |
| ranker_manager_ = std::make_unique<app_list::RankerManager>(profile_); |
| |
| file_suggester_ = std::make_unique<PickerFileSuggester>(profile_); |
| link_suggester_ = std::make_unique<PickerLinkSuggester>(profile_); |
| thumbnail_loader_ = std::make_unique<PickerThumbnailLoader>(profile_); |
| |
| if (controller_ != nullptr) { |
| controller_->OnClientProfileSet(); |
| } |
| } |
| |
| std::unique_ptr<app_list::SearchProvider> |
| PickerClientImpl::CreateOmniboxProvider(bool bookmarks, |
| bool history, |
| bool open_tabs) { |
| if (crosapi::browser_util::IsLacrosEnabled()) { |
| return std::make_unique<app_list::OmniboxLacrosProvider>( |
| profile_, &app_list_controller_delegate_, |
| PickerLacrosOmniboxSearchProvider::CreateControllerCallback( |
| bookmarks, history, open_tabs)); |
| } else { |
| return std::make_unique<app_list::OmniboxProvider>( |
| profile_, &app_list_controller_delegate_, |
| crosapi::ProviderTypesPicker(bookmarks, history, open_tabs)); |
| } |
| } |
| |
| std::unique_ptr<app_list::SearchProvider> |
| PickerClientImpl::CreateSearchProviderForCategory( |
| ash::PickerCategory category) { |
| switch (category) { |
| case ash::PickerCategory::kEditorWrite: |
| case ash::PickerCategory::kEditorRewrite: |
| case ash::PickerCategory::kEmojisGifs: |
| case ash::PickerCategory::kEmojis: |
| case ash::PickerCategory::kClipboard: |
| case ash::PickerCategory::kDatesTimes: |
| case ash::PickerCategory::kUnitsMaths: |
| DLOG(FATAL) << "Unexpected category for autocomplete: " |
| << static_cast<int>(category); |
| return nullptr; |
| case ash::PickerCategory::kLinks: |
| return CreateOmniboxProvider(/*bookmarks=*/true, /*history=*/true, |
| /*open_tabs=*/true); |
| case ash::PickerCategory::kDriveFiles: |
| return CreateDriveSearchProvider(profile_); |
| case ash::PickerCategory::kLocalFiles: |
| return CreateFileSearchProvider(profile_); |
| } |
| } |
| |
| void PickerClientImpl::ShowEditor(std::optional<std::string> preset_query_id, |
| std::optional<std::string> freeform_text) { |
| ash::input_method::EditorMediator* editor_mediator = |
| GetEditorMediator(profile_); |
| if (editor_mediator != nullptr) { |
| editor_mediator->HandleTrigger(std::move(preset_query_id), |
| std::move(freeform_text)); |
| } |
| } |
| |
| PickerClientImpl::PickerAppListControllerDelegate:: |
| PickerAppListControllerDelegate() = default; |
| PickerClientImpl::PickerAppListControllerDelegate:: |
| ~PickerAppListControllerDelegate() = default; |
| |
| void PickerClientImpl::PickerAppListControllerDelegate::DismissView() { |
| NOTIMPLEMENTED_LOG_ONCE(); |
| } |
| |
| aura::Window* |
| PickerClientImpl::PickerAppListControllerDelegate::GetAppListWindow() { |
| NOTIMPLEMENTED_LOG_ONCE(); |
| return nullptr; |
| } |
| |
| int64_t |
| PickerClientImpl::PickerAppListControllerDelegate::GetAppListDisplayId() { |
| NOTIMPLEMENTED_LOG_ONCE(); |
| return 0; |
| } |
| |
| bool PickerClientImpl::PickerAppListControllerDelegate::IsAppPinned( |
| const std::string& app_id) { |
| NOTIMPLEMENTED_LOG_ONCE(); |
| return false; |
| } |
| |
| bool PickerClientImpl::PickerAppListControllerDelegate::IsAppOpen( |
| const std::string& app_id) const { |
| NOTIMPLEMENTED_LOG_ONCE(); |
| return false; |
| } |
| |
| void PickerClientImpl::PickerAppListControllerDelegate::PinApp( |
| const std::string& app_id) { |
| NOTIMPLEMENTED_LOG_ONCE(); |
| } |
| |
| void PickerClientImpl::PickerAppListControllerDelegate::UnpinApp( |
| const std::string& app_id) { |
| NOTIMPLEMENTED_LOG_ONCE(); |
| } |
| |
| AppListControllerDelegate::Pinnable |
| PickerClientImpl::PickerAppListControllerDelegate::GetPinnable( |
| const std::string& app_id) { |
| NOTIMPLEMENTED_LOG_ONCE(); |
| return AppListControllerDelegate::NO_PIN; |
| } |
| |
| void PickerClientImpl::PickerAppListControllerDelegate::CreateNewWindow( |
| bool incognito, |
| bool should_trigger_session_restore) { |
| NOTIMPLEMENTED_LOG_ONCE(); |
| } |
| |
| void PickerClientImpl::PickerAppListControllerDelegate::OpenURL( |
| Profile* profile, |
| const GURL& url, |
| ui::PageTransition transition, |
| WindowOpenDisposition disposition) { |
| NOTIMPLEMENTED_LOG_ONCE(); |
| } |