1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
ash / system / notification_center / ash_notification_drag_controller.cc [blame]
// Copyright 2023 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/system/notification_center/ash_notification_drag_controller.h"
#include "ash/root_window_controller.h"
#include "ash/shell.h"
#include "ash/system/notification_center/views/ash_notification_view.h"
#include "ash/system/notification_center/message_center_utils.h"
#include "ash/system/notification_center/notification_center_tray.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/unified/unified_system_tray.h"
#include "base/metrics/histogram_functions.h"
#include "ui/aura/client/drag_drop_client.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/drop_target_event.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom-shared.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/base/dragdrop/os_exchange_data_provider.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/views/view.h"
namespace ash {
AshNotificationDragController::AshNotificationDragController() = default;
AshNotificationDragController::~AshNotificationDragController() = default;
void AshNotificationDragController::OnDragStarted() {
if (drag_in_progress_) {
// A drag-and-drop session could start before an async drop finishes. In
// this case, neither `OnDropCompleted()` nor `OnDragCancelled()` is called.
// Therefore, clean up the active notification drag handling.
CleanUp(DragEndState::kInterruptedByNewDrag);
} else {
drag_in_progress_ = true;
}
}
void AshNotificationDragController::OnDragCancelled() {
CleanUp(DragEndState::kCancelled);
}
void AshNotificationDragController::OnDropCompleted(
ui::mojom::DragOperation drag_operation) {
// Remove the dragged notification from the message center if drag-and-drop
// ends with copy. `MessageCenter::RemoveNotification()` guarantees that only
// unpinned notifications are removable to users.
DragEndState state = DragEndState::kCompletedWithoutDrop;
if (drag_operation == ui::mojom::DragOperation::kCopy) {
message_center::MessageCenter::Get()->RemoveNotification(
*dragged_notification_id_, /*by_user=*/true);
state = DragEndState::kCompletedWithDrop;
}
CleanUp(state);
}
void AshNotificationDragController::WriteDragDataForView(
views::View* sender,
const gfx::Point& press_pt,
ui::OSExchangeData* data) {
// Sets the image to show during drag.
// TODO(b/308814203): clean the static_cast checks by replacing
// `AshNotificationView*` with a base class.
if (!message_center_utils::IsAshNotificationView(sender)) {
return;
}
AshNotificationView* notification_view =
static_cast<AshNotificationView*>(sender);
const std::optional<gfx::ImageSkia> drag_image =
notification_view->GetDragImage();
DCHECK(drag_image);
// The drag point is at the top left corner, or top right corner under RTL.
data->provider().SetDragImage(
*drag_image, base::i18n::IsRTL()
? gfx::Vector2d(drag_image->size().width(), /*y=*/0)
: gfx::Vector2d());
notification_view->AttachDropData(data);
}
int AshNotificationDragController::GetDragOperationsForView(
views::View* sender,
const gfx::Point& p) {
// TODO(b/308814203): clean the static_cast checks by replacing
// `AshNotificationView*` with a base class.
if (!message_center_utils::IsAshNotificationView(sender)) {
return ui::DragDropTypes::DRAG_NONE;
}
const std::optional<gfx::Rect> drag_area =
static_cast<AshNotificationView*>(sender)->GetDragAreaBounds();
// Use `DRAG_COPY` if:
// 1. `sender` is draggable; and
// 2. `drag_area` contains `p`.
if (drag_area && drag_area->Contains(p)) {
return ui::DragDropTypes::DRAG_COPY;
}
return ui::DragDropTypes::DRAG_NONE;
}
bool AshNotificationDragController::CanStartDragForView(
views::View* sender,
const gfx::Point& press_pt,
const gfx::Point& p) {
// TODO(b/308814203): clean the static_cast checks by replacing
// `AshNotificationView*` with a base class.
if (!message_center_utils::IsAshNotificationView(sender)) {
return false;
}
const AshNotificationView* const notification_view =
static_cast<AshNotificationView*>(sender);
const std::optional<gfx::Rect> drag_area =
notification_view->GetDragAreaBounds();
// Enable dragging `notification_view_` if:
// 1. `notification_view` is backed by a notification; and
// 2. `notification_view` is draggable; and
// 3. `drag_area` contains the initial press point.
const bool can_start_drag =
(!!message_center::MessageCenter::Get()->FindNotificationById(
notification_view->notification_id()) &&
drag_area && drag_area->Contains(press_pt));
// Assume that the drag on `sender` will start when `can_start_drag` is true.
// TODO(crbug.com/40254274): in some edge cases, the view drag does not
// start when `CanStartDragForView()` returns true. We should come up with a
// general solution to observe drag start.
if (can_start_drag) {
// A drag-and-drop session could start before an async drop finishes. In
// this case, neither `OnDropCompleted()` nor `OnDragCancelled()` is called.
// Therefore, clean up the active notification drag handling.
if (drag_in_progress_) {
CleanUp(DragEndState::kInterruptedByNewDrag);
}
}
return can_start_drag;
}
void AshNotificationDragController::OnWillStartDragForView(
views::View* dragged_view) {
// TODO(b/308814203): clean the static_cast checks by replacing
// `AshNotificationView*` with a base class.
if (!message_center_utils::IsAshNotificationView(dragged_view)) {
return;
}
OnNotificationDragWillStart(static_cast<AshNotificationView*>(dragged_view));
}
void AshNotificationDragController::OnNotificationDragWillStart(
AshNotificationView* dragged_view) {
DCHECK(!drag_in_progress_);
dragged_notification_id_ = dragged_view->notification_id();
// The drag drop client in Ash, i.e. `DragDropController`, is a singleton.
// Hence, always use the primary root window to access the drag drop client.
drag_drop_client_observer_.Observe(
aura::client::GetDragDropClient(Shell::GetPrimaryRootWindow()));
message_center::MessageCenter* message_center_ptr =
message_center::MessageCenter::Get();
message_center::Notification* notification =
message_center_ptr->FindNotificationById(*dragged_notification_id_);
base::UmaHistogramEnumeration("Ash.NotificationView.ImageDrag.Start",
notification->notifier_id().catalog_name);
// Hide the message center bubble if it is open.
if (message_center_ptr->IsMessageCenterVisible()) {
StatusAreaWidget* status_area_widget =
RootWindowController::ForWindow(
dragged_view->GetWidget()->GetNativeView())
->GetStatusAreaWidget();
TrayBackgroundView* message_center_bubble = nullptr;
message_center_bubble = status_area_widget->notification_center_tray();
// We cannot destroy the message center bubble instantly. Otherwise, if
// `dragged_view` is under gesture drag, the gesture state will be reset
// when the bubble is closed. Therefore, post a task to close the bubble
// asynchronously.
DCHECK(message_center_bubble);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(
[](const base::WeakPtr<TrayBackgroundView>& weak_ptr) {
if (weak_ptr) {
weak_ptr->CloseBubble();
}
},
message_center_bubble->GetWeakPtr()));
return;
}
// Hide the dragged notification popup if any. Assume that the notification
// popup only shows when the message center is hidden.
// NOTE: if the dragged notification is a child of a notification group, hide
// the group notification popup.
message_center_ptr->MarkSinglePopupAsShown(
notification->group_child()
? message_center_ptr->FindParentNotification(notification)->id()
: *dragged_notification_id_,
/*mark_notification_as_read=*/true);
}
void AshNotificationDragController::CleanUp(DragEndState state) {
DCHECK(drag_in_progress_);
drag_in_progress_ = false;
dragged_notification_id_.reset();
drag_drop_client_observer_.Reset();
base::UmaHistogramEnumeration("Ash.NotificationView.ImageDrag.EndState",
state);
}
} // namespace ash