#include "ash/wm/desks/window_occlusion_calculator.h"
#include <memory>
#include <utility>
#include "ash/public/cpp/window_properties.h"
#include "base/check.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/trace_event/trace_event.h"
#include "ui/aura/window_occlusion_change_builder.h"
#include "ui/aura/window_tracker.h"
namespace ash {
namespace {
void NotifyObserversOfOcclusionChange(
base::ObserverList<WindowOcclusionCalculator::Observer>& observers,
aura::Window* window) {
for (WindowOcclusionCalculator::Observer& obs : observers) {
obs.OnWindowOcclusionChanged(window);
}
}
}
class WindowOcclusionCalculator::WindowOcclusionChangeBuilderImpl
: public aura::WindowOcclusionChangeBuilder {
public:
explicit WindowOcclusionChangeBuilderImpl(
base::WeakPtr<WindowOcclusionCalculator> occlusion_calculator)
: occlusion_calculator_(occlusion_calculator) {}
WindowOcclusionChangeBuilderImpl(const WindowOcclusionChangeBuilderImpl&) =
delete;
WindowOcclusionChangeBuilderImpl& operator=(
const WindowOcclusionChangeBuilderImpl&) = delete;
~WindowOcclusionChangeBuilderImpl() override {
if (!occlusion_calculator_) {
return;
}
for (const auto& [window, occlusion_state] : occlusion_changes_) {
if (window_tracker_.Contains(window)) {
occlusion_calculator_->SetOcclusionState(window, occlusion_state);
}
}
}
void Add(aura::Window* window,
aura::Window::OcclusionState occlusion_state,
SkRegion occluded_region) override {
CHECK_NE(occlusion_state, aura::Window::OcclusionState::UNKNOWN);
occlusion_changes_[window] = occlusion_state;
window_tracker_.Add(window);
}
private:
const base::WeakPtr<WindowOcclusionCalculator> occlusion_calculator_;
WindowOcclusionCalculator::WindowOcclusionMap occlusion_changes_;
aura::WindowTracker window_tracker_;
};
class WindowOcclusionCalculator::ObservationState {
public:
using Observer = WindowOcclusionCalculator::Observer;
ObservationState(aura::Window* parent_window,
aura::WindowOcclusionTracker* occlusion_tracker)
: forced_visibility_(parent_window, occlusion_tracker) {}
ObservationState(const ObservationState&) = delete;
ObservationState& operator=(const ObservationState&) = delete;
~ObservationState() = default;
base::ObserverList<Observer>& observer_list() { return observer_list_; }
private:
base::ObserverList<Observer> observer_list_;
aura::WindowOcclusionTracker::ScopedForceVisible forced_visibility_;
};
WindowOcclusionCalculator::WindowOcclusionCalculator() {
occlusion_tracker_.set_occlusion_change_builder_factory(base::BindRepeating(
[](base::WeakPtr<WindowOcclusionCalculator> occlusion_calculator)
-> std::unique_ptr<aura::WindowOcclusionChangeBuilder> {
return std::make_unique<WindowOcclusionChangeBuilderImpl>(
std::move(occlusion_calculator));
},
weak_ptr_factory_.GetWeakPtr()));
}
WindowOcclusionCalculator::~WindowOcclusionCalculator() {
shutdown_pause_.emplace(&occlusion_tracker_);
}
aura::Window::OcclusionState WindowOcclusionCalculator::GetOcclusionState(
aura::Window* window) const {
if (excluded_windows_.contains(window)) {
return aura::Window::OcclusionState::HIDDEN;
}
auto iter = occlusion_map_.find(window);
return iter == occlusion_map_.end() ? aura::Window::OcclusionState::UNKNOWN
: iter->second;
}
void WindowOcclusionCalculator::AddObserver(
const aura::Window::Windows& parent_windows_to_track,
Observer* observer) {
TRACE_EVENT0("ui", "WindowOcclusionCalculator::AddObserver");
RegisterWindows(parent_windows_to_track);
for (const auto& [parent_window, observation_state] :
occlusion_change_observers_) {
if (base::Contains(parent_windows_to_track, parent_window)) {
observation_state->observer_list().AddObserver(observer);
}
}
}
void WindowOcclusionCalculator::RemoveObserver(Observer* observer) {
TRACE_EVENT0("ui", "WindowOcclusionCalculator::RemoveObserver");
aura::WindowOcclusionTracker::ScopedPause occlusion_calculation_barrier(
&occlusion_tracker_);
auto iter = occlusion_change_observers_.begin();
while (iter != occlusion_change_observers_.end()) {
ObservationState& observation_state = *iter->second;
observation_state.observer_list().RemoveObserver(observer);
if (observation_state.observer_list().empty()) {
iter = occlusion_change_observers_.erase(iter);
} else {
++iter;
}
}
}
void WindowOcclusionCalculator::SnapshotOcclusionStateForWindows(
const aura::Window::Windows& parent_windows_to_snapshot) {
RegisterWindows(parent_windows_to_snapshot);
for (const auto& window : parent_windows_to_snapshot) {
CHECK(snapshot_parent_windows_.insert(window.get()).second)
<< "Requesting multiple snapshots for a window is currently not "
"implemented";
}
}
std::unique_ptr<aura::WindowOcclusionTracker::ScopedPause>
WindowOcclusionCalculator::Pause() {
return std::make_unique<aura::WindowOcclusionTracker::ScopedPause>(
&occlusion_tracker_);
}
base::WeakPtr<WindowOcclusionCalculator>
WindowOcclusionCalculator::AsWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
void WindowOcclusionCalculator::OnWindowHierarchyChanged(
const HierarchyChangeParams& params) {
TRACE_EVENT0("ui", "WindowOcclusionCalculator::OnWindowHierarchyChanged");
if (params.new_parent == params.receiver) {
TrackOcclusionChangesForAllDescendants(params.target);
}
}
void WindowOcclusionCalculator::OnWindowDestroyed(aura::Window* window) {
TRACE_EVENT0("ui", "WindowOcclusionCalculator::OnWindowDestroyed");
all_window_observations_.RemoveObservation(window);
occlusion_map_.erase(window);
occlusion_change_observers_.erase(window);
excluded_windows_.erase(window);
snapshot_parent_windows_.erase(window);
}
void WindowOcclusionCalculator::OnWindowPropertyChanged(aura::Window* window,
const void* key,
intptr_t old) {
TRACE_EVENT0("ui", "WindowOcclusionCalculator::OnWindowPropertyChanged");
if (key != kHideInDeskMiniViewKey) {
return;
}
if (window->GetProperty(kHideInDeskMiniViewKey)) {
ExcludeWindowFromOcclusionCalculation(window);
} else {
excluded_windows_.erase(window);
occlusion_tracker_.Track(window);
}
}
void WindowOcclusionCalculator::RegisterWindows(
const aura::Window::Windows& parent_windows_to_track) {
aura::WindowOcclusionTracker::ScopedPause occlusion_calculation_barrier(
&occlusion_tracker_);
for (const auto& parent_window : parent_windows_to_track) {
occlusion_change_observers_.try_emplace(
parent_window.get(),
std::make_unique<ObservationState>(parent_window, &occlusion_tracker_));
if (!all_window_observations_.IsObservingSource(parent_window)) {
TrackOcclusionChangesForAllDescendants(parent_window.get());
}
}
}
void WindowOcclusionCalculator::SetOcclusionState(
aura::Window* window,
aura::Window::OcclusionState occlusion_state) {
TRACE_EVENT0("ui", "WindowOcclusionCalculator::SetOcclusionState");
if (IsSnapshotWindow(window)) {
return;
}
auto [iter, inserted] = occlusion_map_.try_emplace(window, occlusion_state);
if (!inserted && iter->second == occlusion_state) {
return;
}
iter->second = occlusion_state;
DVLOG(4) << __func__ << " window=" << window->GetName() << " occlusion="
<< aura::Window::OcclusionStateToString(occlusion_state);
for (const auto& [parent_window, observation_state] :
occlusion_change_observers_) {
if (parent_window->Contains(window)) {
NotifyObserversOfOcclusionChange(observation_state->observer_list(),
window);
}
}
}
void WindowOcclusionCalculator::TrackOcclusionChangesForAllDescendants(
aura::Window* window) {
ObserveWindow(window);
for (const auto& child : window->children()) {
TrackOcclusionChangesForAllDescendants(child.get());
}
}
void WindowOcclusionCalculator::ObserveWindow(aura::Window* window) {
if (!all_window_observations_.IsObservingSource(window)) {
all_window_observations_.AddObservation(window);
}
if (window->GetProperty(kHideInDeskMiniViewKey)) {
ExcludeWindowFromOcclusionCalculation(window);
} else {
occlusion_tracker_.Track(window);
}
}
void WindowOcclusionCalculator::ExcludeWindowFromOcclusionCalculation(
aura::Window* window) {
if (excluded_windows_.contains(window)) {
return;
}
excluded_windows_[window] =
std::make_unique<aura::WindowOcclusionTracker::ScopedExclude>(
window, &occlusion_tracker_);
}
bool WindowOcclusionCalculator::IsSnapshotWindow(aura::Window* window) const {
for (const auto& parent_window : snapshot_parent_windows_) {
if (parent_window->Contains(window)) {
return true;
}
}
return false;
}
}