chromium/ash/shelf/shelf_window_watcher.cc

// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/shelf/shelf_window_watcher.h"

#include <memory>
#include <utility>

#include "ash/public/cpp/shelf_model.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/shelf/shelf_window_watcher_item_delegate.h"
#include "ash/shell.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/window_util.h"
#include "base/strings/string_util.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/display/types/display_constants.h"
#include "ui/resources/grit/ui_resources.h"
#include "ui/wm/public/activation_client.h"

namespace ash {
namespace {

// Returns the window's shelf item type property value.
ShelfItemType GetShelfItemType(aura::Window* window) {
  return static_cast<ShelfItemType>(window->GetProperty(kShelfItemTypeKey));
}

// Returns the window's shelf id property value.
ShelfID GetShelfID(aura::Window* window) {
  return ShelfID::Deserialize(window->GetProperty(kShelfIDKey));
}

// Update the ShelfItem from relevant window properties.
void UpdateShelfItemForWindow(ShelfItem* item, aura::Window* window) {
  DCHECK(item->id.IsNull() || item->id == GetShelfID(window));
  item->id = GetShelfID(window);
  item->type = GetShelfItemType(window);
  item->title = window->GetTitle();

  // Active windows don't draw attention because the user is looking at them.
  if (window->GetProperty(aura::client::kDrawAttentionKey) &&
      !wm::IsActiveWindow(window)) {
    item->status = STATUS_ATTENTION;
  } else {
    item->status = STATUS_RUNNING;
  }

  // Prefer app icons over window icons, they're typically larger.
  gfx::ImageSkia* image = window->GetProperty(aura::client::kAppIconKey);
  if (!image || image->isNull())
    image = window->GetProperty(aura::client::kWindowIconKey);
  if (!image || image->isNull()) {
    ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    item->image = rb.GetImageNamed(IDR_DEFAULT_FAVICON_32).AsImageSkia();
  } else {
    item->image = *image;
  }
}

}  // namespace

ShelfWindowWatcher::ContainerWindowObserver::ContainerWindowObserver(
    ShelfWindowWatcher* window_watcher)
    : window_watcher_(window_watcher) {}

ShelfWindowWatcher::ContainerWindowObserver::~ContainerWindowObserver() =
    default;

void ShelfWindowWatcher::ContainerWindowObserver::OnWindowAdded(
    aura::Window* new_window) {
  DCHECK(new_window);
  DCHECK(new_window->parent());
  DCHECK(desks_util::IsDeskContainer(new_window->parent()));
  window_watcher_->OnUserWindowAdded(new_window);
}

void ShelfWindowWatcher::ContainerWindowObserver::OnWindowDestroying(
    aura::Window* window) {
  window_watcher_->OnContainerWindowDestroying(window);
}

////////////////////////////////////////////////////////////////////////////////

ShelfWindowWatcher::UserWindowObserver::UserWindowObserver(
    ShelfWindowWatcher* window_watcher)
    : window_watcher_(window_watcher) {}

ShelfWindowWatcher::UserWindowObserver::~UserWindowObserver() = default;

void ShelfWindowWatcher::UserWindowObserver::OnWindowPropertyChanged(
    aura::Window* window,
    const void* key,
    intptr_t old) {
  if (key == kShelfIDKey && window == window_util::GetActiveWindow()) {
    window_watcher_->model_->SetActiveShelfID(
        ShelfID::Deserialize(window->GetProperty(kShelfIDKey)));
  }

  if (key == aura::client::kAppIconKey || key == aura::client::kWindowIconKey ||
      key == aura::client::kDrawAttentionKey || key == kShelfItemTypeKey ||
      key == kShelfIDKey) {
    window_watcher_->OnUserWindowPropertyChanged(window);
  }
}

void ShelfWindowWatcher::UserWindowObserver::OnWindowDestroying(
    aura::Window* window) {
  window_watcher_->OnUserWindowDestroying(window);
}

void ShelfWindowWatcher::UserWindowObserver::OnWindowVisibilityChanged(
    aura::Window* window,
    bool visible) {
  // This is also called for descendants; check that the window is observed.
  if (window_watcher_->observed_user_windows_.IsObservingSource(window))
    window_watcher_->OnUserWindowPropertyChanged(window);
}

void ShelfWindowWatcher::UserWindowObserver::OnWindowTitleChanged(
    aura::Window* window) {
  window_watcher_->OnUserWindowPropertyChanged(window);
}

////////////////////////////////////////////////////////////////////////////////

ShelfWindowWatcher::ShelfWindowWatcher(ShelfModel* model)
    : model_(model),
      observed_container_windows_(&container_window_observer_),
      observed_user_windows_(&user_window_observer_) {
  Shell::Get()->activation_client()->AddObserver(this);
  Shell::Get()->AddShellObserver(this);
  for (aura::Window* window : Shell::GetAllRootWindows())
    OnRootWindowAdded(window);
}

ShelfWindowWatcher::~ShelfWindowWatcher() {
  Shell::Get()->RemoveShellObserver(this);
  Shell::Get()->activation_client()->RemoveObserver(this);
}

void ShelfWindowWatcher::AddShelfItem(aura::Window* window) {
  user_windows_with_items_.insert(window);
  ShelfItem item;
  UpdateShelfItemForWindow(&item, window);

  model_->AddAt(
      model_->item_count(), item,
      std::make_unique<ShelfWindowWatcherItemDelegate>(item.id, window));
}

void ShelfWindowWatcher::RemoveShelfItem(aura::Window* window) {
  user_windows_with_items_.erase(window);
  const ShelfID shelf_id = GetShelfID(window);
  DCHECK(!shelf_id.IsNull());
  const int index = model_->ItemIndexByID(shelf_id);
  DCHECK_GE(index, 0);
  model_->RemoveItemAt(index);
}

void ShelfWindowWatcher::OnContainerWindowDestroying(aura::Window* container) {
  observed_container_windows_.RemoveObservation(container);
}

void ShelfWindowWatcher::OnUserWindowAdded(aura::Window* window) {
  // The window may already be tracked from a prior display or parent container.
  if (observed_user_windows_.IsObservingSource(window))
    return;

  observed_user_windows_.AddObservation(window);

  // Add, update, or remove a ShelfItem for |window|, as needed.
  OnUserWindowPropertyChanged(window);
}

void ShelfWindowWatcher::OnUserWindowDestroying(aura::Window* window) {
  if (observed_user_windows_.IsObservingSource(window))
    observed_user_windows_.RemoveObservation(window);

  if (user_windows_with_items_.count(window) > 0)
    RemoveShelfItem(window);
  DCHECK_EQ(0u, user_windows_with_items_.count(window));
}

void ShelfWindowWatcher::OnUserWindowPropertyChanged(aura::Window* window) {
  // ShelfWindowWatcher only handles dialogs for now, all other shelf item
  // types are handled by ChromeShelfController.
  const ShelfItemType item_type = GetShelfItemType(window);
  if (item_type != TYPE_DIALOG || GetShelfID(window).IsNull()) {
    // Remove |window|'s ShelfItem if it was added by ShelfWindowWatcher.
    if (user_windows_with_items_.count(window) > 0)
      RemoveShelfItem(window);
    return;
  }

  // Update an existing ShelfWindowWatcher item when a window property changes.
  int index = model_->ItemIndexByID(GetShelfID(window));
  if (index >= 0 && user_windows_with_items_.count(window) > 0) {
    ShelfItem item = model_->items()[index];
    UpdateShelfItemForWindow(&item, window);
    model_->Set(index, item);
    return;
  }

  // Create a new item for |window|, if it is visible.
  if (index < 0 && window->IsVisible())
    AddShelfItem(window);
}

void ShelfWindowWatcher::OnWindowActivated(ActivationReason reason,
                                           aura::Window* gained_active,
                                           aura::Window* lost_active) {
  if (gained_active && user_windows_with_items_.count(gained_active) > 0)
    OnUserWindowPropertyChanged(gained_active);
  if (lost_active && user_windows_with_items_.count(lost_active) > 0)
    OnUserWindowPropertyChanged(lost_active);

  model_->SetActiveShelfID(gained_active ? GetShelfID(gained_active)
                                         : ShelfID());
}

void ShelfWindowWatcher::OnRootWindowAdded(aura::Window* root_window) {
  for (aura::Window* container : desks_util::GetDesksContainers(root_window)) {
    for (aura::Window* window : container->children())
      OnUserWindowAdded(window);
    observed_container_windows_.AddObservation(container);
  }
}

}  // namespace ash