// Copyright 2019 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/shelf/app_service/app_service_app_window_arc_tracker.h"
#include "ash/components/arc/arc_util.h"
#include "ash/public/cpp/multi_user_window_manager.h"
#include "ash/public/cpp/shelf_item_delegate.h"
#include "ash/public/cpp/shelf_model.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/public/cpp/window_properties.h"
#include "base/auto_reset.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "chrome/browser/apps/app_service/app_icon/app_icon_factory.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/ash/app_list/arc/arc_app_utils.h"
#include "chrome/browser/ash/app_list/arc/intent.h"
#include "chrome/browser/ash/app_restore/app_restore_arc_task_handler.h"
#include "chrome/browser/ash/app_restore/arc_ghost_window_handler.h"
#include "chrome/browser/ash/arc/arc_optin_uma.h"
#include "chrome/browser/ash/arc/arc_util.h"
#include "chrome/browser/ash/arc/session/arc_session_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_helper.h"
#include "chrome/browser/ui/ash/shelf/app_service/app_service_app_window_shelf_controller.h"
#include "chrome/browser/ui/ash/shelf/app_service/app_service_app_window_shelf_item_controller.h"
#include "chrome/browser/ui/ash/shelf/app_window_base.h"
#include "chrome/browser/ui/ash/shelf/app_window_shelf_item_controller.h"
#include "chrome/browser/ui/ash/shelf/arc_app_window.h"
#include "chrome/browser/ui/ash/shelf/arc_app_window_info.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h"
#include "chromeos/ui/base/app_types.h"
#include "chromeos/ui/base/window_properties.h"
#include "components/app_restore/full_restore_utils.h"
#include "components/app_restore/window_properties.h"
#include "components/exo/window_properties.h"
#include "extensions/common/constants.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/views/widget/widget.h"
namespace {
constexpr int kArcAppWindowIconSize = extension_misc::EXTENSION_ICON_MEDIUM;
constexpr char kArcPaymentAppPackage[] = "org.chromium.arc.payment_app";
constexpr char kArcPaymentAppInvokePaymentAppActivity[] =
"org.chromium.arc.payment_app.InvokePaymentAppActivity";
// Calculates time delta from the current time and reference time encoded into
// |intent| and defined by |param_key|. Returns false if could not be parsed or
// not found. Result is returned in |out|.
bool GetTimeDeltaFromIntent(const arc::Intent& intent,
const std::string& param_key,
base::TimeDelta* out) {
std::string time_ms_string;
if (!intent.GetExtraParamValue(param_key, &time_ms_string))
return false;
int64_t time_ms;
if (!base::StringToInt64(time_ms_string, &time_ms)) {
LOG(ERROR) << "Failed to parse start time value " << time_ms_string;
return false;
}
*out = (base::TimeTicks::Now() - base::TimeTicks()) -
base::Milliseconds(time_ms);
DCHECK_GE(*out, base::TimeDelta());
return true;
}
void HandlePlayStoreLaunch(const arc::Intent& intent) {
// Don't track initial Play Store launch. We currently shows Play Store in
// very rare case in-session provisioning.
if (intent.HasExtraParam(arc::kInitialStartParam))
return;
base::TimeDelta launch_time;
// This param is injected by |arc:: LaunchAppWithIntent|.
if (!GetTimeDeltaFromIntent(intent, arc::kRequestStartTimeParamKey,
&launch_time)) {
return;
}
arc::UpdatePlayStoreLaunchTime(launch_time);
}
void MaybeHandleDeferredLaunch(const arc::Intent& intent) {
base::TimeDelta launch_time;
// This param is injected by |arc:: LaunchAppWithIntent|.
if (!GetTimeDeltaFromIntent(intent, arc::kRequestDeferredStartTimeParamKey,
&launch_time)) {
return;
}
arc::UpdateDeferredLaunchTime(launch_time);
}
} // namespace
AppServiceAppWindowArcTracker::AppServiceAppWindowArcTracker(
AppServiceAppWindowShelfController* app_service_controller)
: observed_profile_(app_service_controller->owner()->profile()),
app_service_controller_(app_service_controller) {
DCHECK(observed_profile_);
DCHECK(app_service_controller_);
ArcAppListPrefs* const prefs = ArcAppListPrefs::Get(observed_profile_);
DCHECK(prefs);
prefs->AddObserver(this);
arc::ArcSessionManager* arc_session_manager = arc::ArcSessionManager::Get();
// arc::ArcSessionManager might not be set in tests.
if (arc_session_manager)
arc_session_manager->AddObserver(this);
auto* arc_handler = ash::app_restore::AppRestoreArcTaskHandler::GetForProfile(
observed_profile_);
if (arc_handler)
arc_handler->OnShelfReady();
}
AppServiceAppWindowArcTracker::~AppServiceAppWindowArcTracker() {
ArcAppListPrefs* const prefs = ArcAppListPrefs::Get(observed_profile_);
DCHECK(prefs);
prefs->RemoveObserver(this);
arc::ArcSessionManager* arc_session_manager = arc::ArcSessionManager::Get();
// arc::ArcSessionManager may be released first.
if (arc_session_manager)
arc_session_manager->RemoveObserver(this);
}
void AppServiceAppWindowArcTracker::ActiveUserChanged(
const std::string& user_email) {
const std::string& primary_user_email = user_manager::UserManager::Get()
->GetPrimaryUser()
->GetAccountId()
.GetUserEmail();
if (user_email == primary_user_email) {
// Make sure that we created items for all apps, not only which have a
// window.
for (const auto& info : task_id_to_arc_app_window_info_)
AttachControllerToTask(info.first);
// Update active status.
OnTaskSetActive(active_task_id_);
} else {
// Some controllers might have no windows attached, for example background
// task when foreground tasks is in full screen.
for (const auto& it : app_shelf_group_to_controller_map_) {
app_service_controller_->owner()->ReplaceWithAppShortcutOrRemove(
it.second->shelf_id());
}
app_shelf_group_to_controller_map_.clear();
}
}
void AppServiceAppWindowArcTracker::HandleWindowVisibilityChanged(
aura::Window* window) {
auto task_or_session_id = arc::GetWindowTaskOrSessionId(window);
if (!task_or_session_id.has_value() ||
*task_or_session_id == arc::kSystemWindowTaskId) {
return;
}
// Attach window to multi-user manager now to let it manage visibility state
// of the ARC window correctly.
MultiUserWindowManagerHelper::GetWindowManager()->SetWindowOwner(
window,
user_manager::UserManager::Get()->GetPrimaryUser()->GetAccountId());
}
void AppServiceAppWindowArcTracker::HandleWindowActivatedChanged(
aura::Window* window) {
OnTaskSetActive(active_task_id_);
active_session_id_ = arc::GetWindowSessionId(window).value_or(arc::kNoTaskId);
}
void AppServiceAppWindowArcTracker::HandleWindowDestroying(
aura::Window* window) {
app_service_controller_->UnregisterWindow(window);
// Replace the pointers to the window by nullptr to prevent from using it
// before OnTaskDestroyed() is called to remove the entry from
// |task_id_to_arc_app_window_info_|;
ArcAppWindowInfo* info = GetArcAppWindowInfo(window);
if (info)
info->set_window(nullptr);
auto session_id = arc::GetWindowSessionId(window);
if (session_id.has_value()) {
OnSessionDestroyed(*session_id);
session_id_to_arc_app_window_info_.erase(*session_id);
if (session_id == active_session_id_)
active_session_id_ = arc::kNoTaskId;
}
}
void AppServiceAppWindowArcTracker::CloseWindows(const std::string& app_id) {
const std::vector<int> task_ids = GetTaskIdsForApp(app_id);
for (const auto task_id : task_ids)
arc::CloseTask(task_id);
}
void AppServiceAppWindowArcTracker::OnWindowPropertyChanged(
aura::Window* window,
const void* key,
intptr_t old) {
if (key != exo::kApplicationIdKey || old == 0)
return;
const std::string* old_val = reinterpret_cast<std::string*>(old);
const auto maybe_session_id = arc::GetSessionIdFromWindowAppId(*old_val);
const auto maybe_task_id = arc::GetWindowTaskId(window);
if (maybe_session_id.has_value() && maybe_task_id.has_value()) {
session_id_to_task_id_map_.erase(maybe_session_id.value());
}
}
void AppServiceAppWindowArcTracker::OnAppStatesChanged(
const std::string& app_id,
const ArcAppListPrefs::AppInfo& app_info) {
if (!app_info.ready)
OnAppRemoved(app_id);
}
void AppServiceAppWindowArcTracker::OnAppRemoved(const std::string& app_id) {
const std::vector<int> task_ids_to_remove = GetTaskIdsForApp(app_id);
for (const auto task_id : task_ids_to_remove)
OnTaskDestroyed(task_id);
DCHECK(GetTaskIdsForApp(app_id).empty());
const std::vector<int> session_ids_to_remove = GetSessionIdsForApp(app_id);
for (const auto session_id : session_ids_to_remove)
OnSessionDestroyed(session_id);
DCHECK(GetSessionIdsForApp(app_id).empty());
}
void AppServiceAppWindowArcTracker::OnTaskCreated(
int32_t task_id,
const std::string& package_name,
const std::string& activity_name,
const std::string& intent,
int32_t session_id) {
base::AutoReset<int> auto_reset(&task_id_being_created_, task_id);
DCHECK(task_id_to_arc_app_window_info_.find(task_id) ==
task_id_to_arc_app_window_info_.end());
// If there is a ghost window for `session_id`, reuse the ghost window info,
// and clear the ghost window info from `session_id_to_arc_app_window_info_`,
// and reset `active_session_id_`.
auto it = session_id_to_arc_app_window_info_.find(session_id);
if (it != session_id_to_arc_app_window_info_.end()) {
const auto app_shelf_id = it->second->app_shelf_id();
task_id_to_arc_app_window_info_[task_id] =
std::make_unique<ArcAppWindowInfo>(app_shelf_id, intent, package_name);
session_id_to_task_id_map_[session_id] = task_id;
task_id_to_arc_app_window_info_[task_id]->set_window(it->second->window());
auto it_controller = app_shelf_group_to_controller_map_.find(app_shelf_id);
if (it_controller != app_shelf_group_to_controller_map_.end())
it_controller->second->RemoveSessionId(it->first);
session_id_to_arc_app_window_info_.erase(it);
if (session_id == active_session_id_)
active_session_id_ = arc::kNoTaskId;
} else {
const std::string arc_app_id =
ArcAppListPrefs::GetAppId(package_name, activity_name);
const arc::ArcAppShelfId arc_app_shelf_id =
arc::ArcAppShelfId::FromIntentAndAppId(intent, arc_app_id);
task_id_to_arc_app_window_info_[task_id] =
std::make_unique<ArcAppWindowInfo>(arc_app_shelf_id, intent,
package_name);
}
// Hide from shelf if there already is some task representing the window.
if (GetTaskIdSharingLogicalWindow(task_id) != arc::kNoTaskId) {
task_id_to_arc_app_window_info_[task_id]->set_window_hidden_from_shelf(
true);
}
// Hide any activities created from the ARC Payment activitity from the shelf
// (they become overlays of TWA apps already on the shelf)
if ((package_name == kArcPaymentAppPackage) &&
(activity_name == kArcPaymentAppInvokePaymentAppActivity)) {
task_id_to_arc_app_window_info_[task_id]->set_task_hidden_from_shelf();
}
CheckAndAttachControllers();
// Some tasks can be started in background and might have no window until
// pushed to the front. We need its representation on the shelf to give a user
// control over it.
AttachControllerToTask(task_id);
// TODO(crbug.com/40808991): Investigate why `task_id_to_arc_app_window_info_`
// doesn't have the `task_id` or why it->second is null.
auto task_id_it = task_id_to_arc_app_window_info_.find(task_id);
if (task_id_it == task_id_to_arc_app_window_info_.end() ||
!task_id_it->second) {
return;
}
aura::Window* const window = task_id_it->second->window();
if (!window)
return;
// If we found the window, update AppService InstanceRegistry to add the
// window information.
// Update |state|. The app must be started, and running state. If visible,
// set it as |kVisible|, otherwise, clear the visible bit.
auto* proxy = apps::AppServiceProxyFactory::GetForProfile(observed_profile_);
apps::InstanceState state = proxy->InstanceRegistry().GetState(window);
state = static_cast<apps::InstanceState>(
state | apps::InstanceState::kStarted | apps::InstanceState::kRunning);
app_service_controller_->app_service_instance_helper()->OnInstances(
task_id_to_arc_app_window_info_[task_id]->app_shelf_id().app_id(), window,
std::string(), state);
arc_window_candidates_.erase(window);
}
void AppServiceAppWindowArcTracker::OnTaskDescriptionChanged(
int32_t task_id,
const std::string& label,
const arc::mojom::RawIconPngData& icon,
uint32_t primary_color,
uint32_t status_bar_color) {
auto it = task_id_to_arc_app_window_info_.find(task_id);
if (it == task_id_to_arc_app_window_info_.end())
return;
// If |icon| is empty, and non-adaptive icon as the default value, don't
// call ArcRawIconPngDataToImageSkia, because it might return the default
// play store icon to replace the app icon.
if (!icon.is_adaptive_icon &&
(!icon.icon_png_data.has_value() || icon.icon_png_data.value().empty())) {
return;
}
apps::ArcRawIconPngDataToImageSkia(
icon.Clone(), kArcAppWindowIconSize,
base::BindOnce(&AppServiceAppWindowArcTracker::OnIconLoaded,
weak_ptr_factory_.GetWeakPtr(), task_id, label));
}
void AppServiceAppWindowArcTracker::OnTaskDestroyed(int32_t task_id) {
// Update crbug.com/1276603 with crash stack if this CHECK fires.
CHECK_NE(task_id_being_created_, task_id);
auto it = task_id_to_arc_app_window_info_.find(task_id);
if (it == task_id_to_arc_app_window_info_.end())
return;
if (!it->second->logical_window_id().empty()) {
const int other_id = GetTaskIdSharingLogicalWindow(task_id);
if (other_id != arc::kNoTaskId)
task_id_to_arc_app_window_info_[other_id]->set_window_hidden_from_shelf(
false);
}
aura::Window* const window = it->second.get()->window();
if (window) {
// For ARC apps, window may be recreated in some cases, and OnTaskSetActive
// could be called after the window is destroyed, so controller is not
// closed on window destroying. Controller will be closed onTaskDestroyed
// event which is generated when the actual task is destroyed. So when the
// task is destroyed, delete the instance, otherwise, we might have an
// instance though the window has been closed, and the task has been
// destroyed.
app_service_controller_->app_service_instance_helper()->OnInstances(
it->second.get()->app_shelf_id().app_id(), window, std::string(),
apps::InstanceState::kDestroyed);
app_service_controller_->UnregisterWindow(window);
}
// Check if we may close controller now, at this point we can safely remove
// controllers without window.
const auto app_shelf_id = it->second->app_shelf_id();
auto it_controller = app_shelf_group_to_controller_map_.find(app_shelf_id);
if (it_controller != app_shelf_group_to_controller_map_.end()) {
it_controller->second->RemoveTaskId(task_id);
if (!it_controller->second->HasAnyTasks() &&
!it_controller->second->HasAnySessions()) {
app_service_controller_->owner()->ReplaceWithAppShortcutOrRemove(
it_controller->second->shelf_id());
app_shelf_group_to_controller_map_.erase(app_shelf_id);
}
}
task_id_to_arc_app_window_info_.erase(task_id);
}
void AppServiceAppWindowArcTracker::OnTaskSetActive(int32_t task_id) {
if (observed_profile_ != app_service_controller_->owner()->profile()) {
active_task_id_ = task_id;
return;
}
if (task_id == active_task_id_)
return;
auto* helper = app_service_controller_->app_service_instance_helper();
auto it = task_id_to_arc_app_window_info_.find(active_task_id_);
if (it != task_id_to_arc_app_window_info_.end()) {
ArcAppWindowInfo* const previous_arc_app_window_info = it->second.get();
DCHECK(previous_arc_app_window_info);
app_service_controller_->owner()->SetItemStatus(
previous_arc_app_window_info->shelf_id(), ash::STATUS_RUNNING);
auto* window = previous_arc_app_window_info->window();
AppWindowBase* previous_app_window =
app_service_controller_->GetAppWindow(window);
if (previous_app_window) {
previous_app_window->SetFullscreenMode(
previous_app_window->widget() &&
previous_app_window->widget()->IsFullscreen()
? ArcAppWindow::FullScreenMode::kActive
: ArcAppWindow::FullScreenMode::kNonActive);
}
if (window) {
apps::InstanceState state =
helper->CalculateActivatedState(window, false /* active */);
helper->OnInstances(previous_arc_app_window_info->app_shelf_id().app_id(),
window, std::string(), state);
}
}
active_task_id_ = task_id;
it = task_id_to_arc_app_window_info_.find(active_task_id_);
if (it == task_id_to_arc_app_window_info_.end())
return;
ArcAppWindowInfo* const current_arc_app_window_info = it->second.get();
if (!current_arc_app_window_info || !current_arc_app_window_info->window())
return;
aura::Window* const window = current_arc_app_window_info->window();
views::Widget* const widget = views::Widget::GetWidgetForNativeWindow(window);
DCHECK(widget);
if (widget && widget->IsActive()) {
auto* controller = app_service_controller_->ControllerForWindow(window);
if (controller)
controller->SetActiveWindow(window);
}
app_service_controller_->owner()->SetItemStatus(
current_arc_app_window_info->shelf_id(), ash::STATUS_RUNNING);
apps::InstanceState state =
helper->CalculateActivatedState(window, true /* active */);
helper->OnInstances(current_arc_app_window_info->app_shelf_id().app_id(),
window, std::string(), state);
}
void AppServiceAppWindowArcTracker::AttachControllerToWindow(
aura::Window* window) {
auto task_or_session_id = arc::GetWindowTaskOrSessionId(window);
if (!task_or_session_id.has_value())
return;
// System windows are also arc apps.
window->SetProperty(chromeos::kAppTypeKey, chromeos::AppType::ARC_APP);
if (*task_or_session_id == arc::kSystemWindowTaskId)
return;
ArcAppWindowInfo* const info = GetArcAppWindowInfo(window);
if (!info)
return;
window->SetProperty(ash::kArcPackageNameKey, info->package_name());
window->SetProperty<int>(ash::kShelfItemTypeKey, ash::TYPE_APP);
// Check if we have set the AppWindowBase for this task. If it was a session
// window (ARC ghost window) and replace by real task window, function will
// returen here.
if (app_service_controller_->GetAppWindow(window))
return;
views::Widget* const widget = views::Widget::GetWidgetForNativeWindow(window);
DCHECK(widget);
info->set_window(window);
const ash::ShelfID shelf_id = info->shelf_id();
const auto task_id = arc::GetWindowTaskId(window);
const auto session_id = arc::GetWindowSessionId(window);
if (task_id.has_value())
AttachControllerToTask(*task_id);
else if (session_id.has_value())
AttachControllerToSession(*session_id, *info);
if (!info->task_hidden_from_shelf())
app_service_controller_->AddWindowToShelf(window, shelf_id);
AppWindowBase* app_window = app_service_controller_->GetAppWindow(window);
if (app_window)
app_window->SetDescription(info->title(), info->icon());
window->SetProperty(ash::kShelfIDKey, shelf_id.Serialize());
window->SetProperty(ash::kAppIDKey, shelf_id.app_id);
window->SetProperty(aura::client::kSkipImeProcessing, true);
if (info->launch_intent().empty())
return;
auto intent = arc::Intent::Get(info->launch_intent());
if (!intent) {
LOG(ERROR) << "Failed to parse launch intent: " << info->launch_intent();
return;
}
if (info->app_shelf_id().app_id() == arc::kPlayStoreAppId)
HandlePlayStoreLaunch(*intent);
MaybeHandleDeferredLaunch(*intent);
}
void AppServiceAppWindowArcTracker::AddCandidateWindow(aura::Window* window) {
arc_window_candidates_.insert(window);
}
void AppServiceAppWindowArcTracker::RemoveCandidateWindow(
aura::Window* window) {
arc_window_candidates_.erase(window);
}
void AppServiceAppWindowArcTracker::OnItemDelegateDiscarded(
const ash::ShelfID& shelf_id,
ash::ShelfItemDelegate* delegate) {
arc::ArcAppShelfId app_shelf_id =
arc::ArcAppShelfId::FromString(shelf_id.app_id);
auto it = app_shelf_group_to_controller_map_.find(app_shelf_id);
if (it != app_shelf_group_to_controller_map_.end() &&
static_cast<ash::ShelfItemDelegate*>(it->second) == delegate) {
app_shelf_group_to_controller_map_.erase(it);
}
}
ash::ShelfID AppServiceAppWindowArcTracker::GetShelfId(aura::Window* window) {
if (observed_profile_ != app_service_controller_->owner()->profile())
return ash::ShelfID();
ArcAppWindowInfo* info = GetArcAppWindowInfo(window);
if (!info)
return ash::ShelfID();
return info->shelf_id();
}
void AppServiceAppWindowArcTracker::CheckAndAttachControllers() {
for (aura::Window* window : arc_window_candidates_) {
AttachControllerToWindow(window);
}
}
void AppServiceAppWindowArcTracker::AttachControllerToTask(int task_id) {
// TODO(crbug.com/40808991): Investigate why `task_id_to_arc_app_window_info_`
// doesn't have the `task_id` or why it->second is null.
auto it = task_id_to_arc_app_window_info_.find(task_id);
if (it == task_id_to_arc_app_window_info_.end() || !it->second)
return;
ArcAppWindowInfo* const app_window_info = it->second.get();
if (app_window_info->task_hidden_from_shelf())
return;
const arc::ArcAppShelfId& app_shelf_id = app_window_info->app_shelf_id();
if (base::Contains(app_shelf_group_to_controller_map_, app_shelf_id)) {
app_shelf_group_to_controller_map_[app_shelf_id]->AddTaskId(task_id);
return;
}
const ash::ShelfID shelf_id(app_shelf_id.ToString());
std::unique_ptr<AppServiceAppWindowShelfItemController> controller =
std::make_unique<AppServiceAppWindowShelfItemController>(
shelf_id, app_service_controller_);
AppServiceAppWindowShelfItemController* item_controller = controller.get();
if (!app_service_controller_->owner()->GetItem(shelf_id)) {
app_service_controller_->owner()->CreateAppItem(
std::move(controller), ash::STATUS_RUNNING, /*pinned=*/false);
} else {
app_service_controller_->owner()->shelf_model()->ReplaceShelfItemDelegate(
shelf_id, std::move(controller));
app_service_controller_->owner()->SetItemStatus(shelf_id,
ash::STATUS_RUNNING);
}
item_controller->AddTaskId(task_id);
app_shelf_group_to_controller_map_[app_shelf_id] = item_controller;
}
void AppServiceAppWindowArcTracker::AttachControllerToSession(
int session_id,
const ArcAppWindowInfo& app_window_info) {
// Pass the ArcAppWindowInfo as the parameter, since in some case the window
// is the task window but still associated with session id due to async issue.
// In that case, we cannot use the info from the
// `session_id_to_arc_app_window_info_` directly.
// TODO(b/274950968): Add a test for this case.
const arc::ArcAppShelfId& app_shelf_id = app_window_info.app_shelf_id();
if (base::Contains(app_shelf_group_to_controller_map_, app_shelf_id)) {
app_shelf_group_to_controller_map_[app_shelf_id]->AddSessionId(session_id);
return;
}
const ash::ShelfID shelf_id(app_shelf_id.ToString());
std::unique_ptr<AppServiceAppWindowShelfItemController> controller =
std::make_unique<AppServiceAppWindowShelfItemController>(
shelf_id, app_service_controller_);
AppServiceAppWindowShelfItemController* item_controller = controller.get();
if (!app_service_controller_->owner()->GetItem(shelf_id)) {
app_service_controller_->owner()->CreateAppItem(
std::move(controller), ash::STATUS_RUNNING, /*pinned=*/false);
} else {
app_service_controller_->owner()->shelf_model()->ReplaceShelfItemDelegate(
shelf_id, std::move(controller));
app_service_controller_->owner()->SetItemStatus(shelf_id,
ash::STATUS_RUNNING);
}
item_controller->AddSessionId(session_id);
app_shelf_group_to_controller_map_[app_shelf_id] = item_controller;
}
void AppServiceAppWindowArcTracker::OnArcPlayStoreEnabledChanged(bool enabled) {
if (enabled)
return;
// If ARC was disabled, close the ghost window.
std::vector<int> session_ids;
for (const auto& it : session_id_to_arc_app_window_info_)
session_ids.push_back(it.first);
for (const auto session_id : session_ids)
OnSessionDestroyed(session_id);
DCHECK(session_id_to_arc_app_window_info_.empty());
}
int AppServiceAppWindowArcTracker::GetTaskIdSharingLogicalWindow(int task_id) {
auto fixed_it = task_id_to_arc_app_window_info_.find(task_id);
if (fixed_it == task_id_to_arc_app_window_info_.end())
return arc::kNoTaskId;
if (fixed_it->second->logical_window_id().empty())
return arc::kNoTaskId;
for (auto it = task_id_to_arc_app_window_info_.begin();
it != task_id_to_arc_app_window_info_.end(); it++) {
if (task_id == it->first)
continue;
if (fixed_it->second->logical_window_id() ==
it->second->logical_window_id() &&
fixed_it->second->shelf_id() == it->second->shelf_id()) {
return it->first;
}
}
return arc::kNoTaskId;
}
std::vector<int> AppServiceAppWindowArcTracker::GetTaskIdsForApp(
const std::string& app_id) const {
std::vector<int> task_ids;
for (const auto& it : task_id_to_arc_app_window_info_) {
const ArcAppWindowInfo* app_window_info = it.second.get();
if (app_window_info->app_shelf_id().app_id() == app_id)
task_ids.push_back(it.first);
}
return task_ids;
}
std::vector<int> AppServiceAppWindowArcTracker::GetSessionIdsForApp(
const std::string& app_id) const {
std::vector<int> session_ids;
for (const auto& it : session_id_to_arc_app_window_info_) {
const ArcAppWindowInfo* app_window_info = it.second.get();
if (app_window_info->app_shelf_id().app_id() == app_id)
session_ids.push_back(it.first);
}
return session_ids;
}
void AppServiceAppWindowArcTracker::OnIconLoaded(int32_t task_id,
const std::string& title,
const gfx::ImageSkia& icon) {
auto it = task_id_to_arc_app_window_info_.find(task_id);
if (it == task_id_to_arc_app_window_info_.end())
return;
ArcAppWindowInfo* const info = it->second.get();
DCHECK(info);
info->SetDescription(title, icon);
AppWindowBase* app_window =
app_service_controller_->GetAppWindow(it->second->window());
if (app_window)
app_window->SetDescription(title, icon);
}
ArcAppWindowInfo* AppServiceAppWindowArcTracker::GetArcAppWindowInfo(
aura::Window* window) {
const auto task_id = arc::GetWindowTaskId(window);
if (task_id.has_value()) {
auto it = task_id_to_arc_app_window_info_.find(*task_id);
if (it != task_id_to_arc_app_window_info_.end())
return it->second.get();
}
const auto session_id = arc::GetWindowSessionId(window);
if (!session_id.has_value())
return nullptr;
// Since OnTaskCreated is async with corresponding aura window create, so in
// some cases, the task has beend created but window property in aura window
// haven't been updated and still kept "session_id". In this case, if the
// session's task created, just return the latest task info.
if (base::Contains(session_id_to_task_id_map_, *session_id)) {
auto mapped_task_id = session_id_to_task_id_map_[*session_id];
if (base::Contains(task_id_to_arc_app_window_info_, mapped_task_id))
return task_id_to_arc_app_window_info_[mapped_task_id].get();
}
const std::string* arc_app_id = window->GetProperty(app_restore::kAppIdKey);
if (!arc_app_id || arc_app_id->empty())
return nullptr;
auto session_id_it = session_id_to_arc_app_window_info_.find(*session_id);
if (session_id_it != session_id_to_arc_app_window_info_.end())
return session_id_it->second.get();
const arc::ArcAppShelfId arc_app_shelf_id =
arc::ArcAppShelfId::FromIntentAndAppId(/*intent=*/std::string(),
*arc_app_id);
session_id_to_arc_app_window_info_[*session_id] =
std::make_unique<ArcAppWindowInfo>(arc_app_shelf_id,
/*intent=*/std::string(),
/*package_name=*/std::string());
return session_id_to_arc_app_window_info_[*session_id].get();
}
void AppServiceAppWindowArcTracker::OnSessionDestroyed(int32_t session_id) {
auto it = session_id_to_arc_app_window_info_.find(session_id);
if (it == session_id_to_arc_app_window_info_.end())
return;
aura::Window* const window = it->second.get()->window();
if (window) {
app_service_controller_->app_service_instance_helper()->OnInstances(
it->second.get()->app_shelf_id().app_id(), window, std::string(),
apps::InstanceState::kDestroyed);
app_service_controller_->UnregisterWindow(window);
}
// Check if we may close controller now, at this point we can safely remove
// controllers without window.
const auto app_shelf_id = it->second->app_shelf_id();
auto it_controller = app_shelf_group_to_controller_map_.find(app_shelf_id);
if (it_controller != app_shelf_group_to_controller_map_.end()) {
it_controller->second->RemoveSessionId(session_id);
if (!it_controller->second->HasAnyTasks() &&
!it_controller->second->HasAnySessions()) {
app_service_controller_->owner()->ReplaceWithAppShortcutOrRemove(
it_controller->second->shelf_id());
app_shelf_group_to_controller_map_.erase(app_shelf_id);
}
}
session_id_to_arc_app_window_info_.erase(session_id);
// Close the ghost window.
auto* arc_handler = ash::app_restore::AppRestoreArcTaskHandler::GetForProfile(
observed_profile_);
if (arc_handler && arc_handler->window_handler())
arc_handler->window_handler()->CloseWindow(session_id);
}