[go: nahoru, domu]

blob: 49119d99145fdd05176210a202ac7e1d09946b1b [file] [log] [blame]
// Copyright 2020 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/ash/app_list/app_list_notifier_impl.h"
#include "ash/public/cpp/app_list/app_list_controller.h"
#include "base/check.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "ui/display/types/display_constants.h"
namespace {
// TODO(crbug.com/1076270): Finalize a value for this, and possibly use
// different values for different UI surfaces.
constexpr base::TimeDelta kImpressionTimer = base::Seconds(1);
} // namespace
AppListNotifierImpl::AppListNotifierImpl(
ash::AppListController* app_list_controller)
: app_list_controller_(app_list_controller) {
DCHECK(app_list_controller_);
app_list_controller_->AddObserver(this);
OnAppListVisibilityWillChange(app_list_controller_->IsVisible(),
display::kInvalidDisplayId);
}
AppListNotifierImpl::~AppListNotifierImpl() {
app_list_controller_->RemoveObserver(this);
}
void AppListNotifierImpl::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void AppListNotifierImpl::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void AppListNotifierImpl::NotifyLaunched(Location location,
const Result& result) {
launched_result_ = result;
// The continue and recent apps appear at the same time. If a launch occurs in
// one, mark the other as 'ignored' rather than abandoned.
if (location == Location::kContinue) {
DoStateTransition(Location::kRecentApps, State::kIgnored);
} else if (location == Location::kRecentApps) {
DoStateTransition(Location::kContinue, State::kIgnored);
}
DoStateTransition(location, State::kLaunched);
}
void AppListNotifierImpl::NotifyResultsUpdated(
Location location,
const std::vector<Result>& results) {
if (location == Location::kList) {
for (const auto& result : results)
list_results_[result.id] = result;
} else if (location == Location::kAnswerCard ||
location == Location::kImage) {
if (results.size() > 0) {
DoStateTransition(location, State::kShown);
} else {
DoStateTransition(location, State::kNone);
}
results_[location] = results;
} else {
results_[location] = results;
}
}
void AppListNotifierImpl::NotifyContinueSectionVisibilityChanged(
Location location,
bool visible) {
DCHECK(location == Location::kContinue || location == Location::kRecentApps);
continue_section_visibility_[location] = visible;
DoStateTransition(location, shown_ && query_.empty() && visible
? State::kShown
: State::kNone);
}
bool AppListNotifierImpl::GetContinueSectionVisibility(
Location location) const {
const auto it = continue_section_visibility_.find(location);
return it != continue_section_visibility_.cend() && it->second;
}
void AppListNotifierImpl::NotifySearchQueryChanged(
const std::u16string& query) {
// In some cases the query can change after the launcher is closed, in
// particular this happens when abandoning the launcher with a non-empty
// query. Only do a state transition if the launcher is open.
if (shown_) {
if (query.empty()) {
DoStateTransition(Location::kList, State::kNone);
DoStateTransition(Location::kAnswerCard, State::kNone);
DoStateTransition(Location::kContinue,
GetContinueSectionVisibility(Location::kContinue)
? State::kShown
: State::kNone);
DoStateTransition(Location::kRecentApps,
GetContinueSectionVisibility(Location::kRecentApps)
? State::kShown
: State::kNone);
} else {
if (!search_session_in_progress_) {
search_session_in_progress_ = true;
for (auto& observer : observers_) {
observer.OnSearchSessionStarted();
}
}
DoStateTransition(Location::kList, State::kShown);
DoStateTransition(Location::kAnswerCard, State::kNone);
DoStateTransition(Location::kContinue, State::kNone);
DoStateTransition(Location::kRecentApps, State::kNone);
}
}
// Update the stored |query_| after performing the state transitions, so that
// an abandon triggered by the query change correctly uses the pre-abandon
// query.
query_ = query;
results_.clear();
list_results_.clear();
for (auto& observer : observers_) {
observer.OnQueryChanged(query);
}
}
bool AppListNotifierImpl::FireImpressionTimerForTesting(Location location) {
auto timer_it = timers_.find(location);
if (timer_it == timers_.end() || !timer_it->second->IsRunning())
return false;
timer_it->second->FireNow();
return true;
}
void AppListNotifierImpl::OnAppListVisibilityWillChange(bool shown,
int64_t display_id) {
if (shown_ == shown)
return;
shown_ = shown;
if (shown) {
if (GetContinueSectionVisibility(Location::kContinue))
DoStateTransition(Location::kContinue, State::kShown);
if (GetContinueSectionVisibility(Location::kRecentApps))
DoStateTransition(Location::kRecentApps, State::kShown);
// kList is not shown until a search query is entered.
} else {
if (search_session_in_progress_) {
search_session_in_progress_ = false;
for (auto& observer : observers_) {
observer.OnSearchSessionEnded(query_);
}
}
DoStateTransition(Location::kList, State::kNone);
DoStateTransition(Location::kContinue, State::kNone);
DoStateTransition(Location::kRecentApps, State::kNone);
DoStateTransition(Location::kAnswerCard, State::kNone);
}
}
void AppListNotifierImpl::OnViewStateChanged(ash::AppListViewState state) {
if (state == ash::AppListViewState::kFullscreenSearch && !query_.empty()) {
search_session_in_progress_ = true;
for (auto& observer : observers_) {
observer.OnSearchSessionStarted();
}
} else {
search_session_in_progress_ = false;
for (auto& observer : observers_) {
observer.OnSearchSessionEnded(query_);
}
}
}
void AppListNotifierImpl::RestartTimer(Location location) {
if (timers_.find(location) == timers_.end()) {
timers_[location] = std::make_unique<base::OneShotTimer>();
}
auto& timer = timers_[location];
if (timer->IsRunning()) {
timer->Stop();
}
// base::Unretained is safe here because the timer is a member of |this|, and
// OneShotTimer cancels its timer on destruction.
timer->Start(FROM_HERE, kImpressionTimer,
base::BindOnce(&AppListNotifierImpl::OnTimerFinished,
base::Unretained(this), location));
}
void AppListNotifierImpl::StopTimer(Location location) {
const auto it = timers_.find(location);
if (it != timers_.end() && it->second->IsRunning()) {
it->second->Stop();
}
}
void AppListNotifierImpl::OnTimerFinished(Location location) {
DoStateTransition(location, State::kSeen);
}
std::vector<AppListNotifierImpl::Result>
AppListNotifierImpl::ResultsForLocation(Location location) {
// Special case kList, see header comment on |list_results_|.
if (location == Location::kList) {
std::vector<Result> results;
for (const auto& id_result : list_results_) {
results.push_back(id_result.second.value());
}
return results;
}
return results_[location];
}
void AppListNotifierImpl::DoStateTransition(Location location,
State new_state) {
const State old_state = states_[location];
// Update most recent state. We special-case kLaunched and kIgnored, which are
// temporary states reflecting a launch either in |location| or another view.
// They immediately transition to kNone because the launcher closes after a
// launch.
if (new_state == State::kLaunched || new_state == State::kIgnored) {
states_[location] = State::kNone;
} else {
states_[location] = new_state;
}
// These overlapping cases are equivalent to the explicit cases in the header
// comment.
// Restart timer on * -> kShown
if (new_state == State::kShown) {
RestartTimer(location);
}
// Stop timer on kShown -> {kNone, kLaunch}.
if (old_state == State::kShown &&
(new_state == State::kNone || new_state == State::kLaunched)) {
StopTimer(location);
}
// Notify of seen on kShown -> {kSeen} when there is one or more result for
// `location`.
if (old_state == State::kShown && new_state == State::kSeen) {
auto results = ResultsForLocation(location);
if (results.size() > 0) {
for (auto& observer : observers_) {
observer.OnSeen(location, results, query_);
}
}
}
// Notify of impression on kShown -> {kSeen, kIgnored, kLaunched}.
if (old_state == State::kShown &&
(new_state == State::kSeen || new_state == State::kLaunched ||
new_state == State::kIgnored)) {
for (auto& observer : observers_) {
observer.OnImpression(location, ResultsForLocation(location), query_);
}
}
// Notify of launch on * -> kLaunched.
if (new_state == State::kLaunched && launched_result_.has_value()) {
for (auto& observer : observers_) {
observer.OnLaunch(location, launched_result_.value(),
ResultsForLocation(location), query_);
}
}
// Notify of ignore on * -> kIgnored.
if (new_state == State::kIgnored) {
for (auto& observer : observers_) {
observer.OnIgnore(location, ResultsForLocation(location), query_);
}
}
// Notify of abandon on kSeen -> {kNone, kShown}.
if (old_state == State::kSeen &&
(new_state == State::kNone || new_state == State::kShown)) {
for (auto& observer : observers_) {
observer.OnAbandon(location, ResultsForLocation(location), query_);
}
}
}