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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
ash / system / eche / eche_tray.h [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef ASH_SYSTEM_ECHE_ECHE_TRAY_H_
#define ASH_SYSTEM_ECHE_ECHE_TRAY_H_
#include <string>
#include "ash/ash_export.h"
#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
#include "ash/public/cpp/session/session_observer.h"
#include "ash/shelf/shelf_observer.h"
#include "ash/shell_observer.h"
#include "ash/system/eche/eche_icon_loading_indicator_view.h"
#include "ash/system/screen_layout_observer.h"
#include "ash/system/tray/system_tray_observer.h"
#include "ash/system/tray/tray_background_view.h"
#include "ash/webui/eche_app_ui/eche_connection_status_handler.h"
#include "ash/webui/eche_app_ui/mojom/eche_app.mojom-shared.h"
#include "ash/webui/eche_app_ui/mojom/eche_app.mojom.h"
#include "base/functional/callback_forward.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/timer/timer.h"
#include "ui/display/display_observer.h"
#include "ui/display/tablet_state.h"
#include "ui/events/event_handler.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/controls/button/button.h"
#include "url/gurl.h"
namespace display {
enum class TabletState;
} // namespace display
namespace views {
class ImageView;
class ImageButton;
class View;
class Widget;
} // namespace views
namespace ui {
class KeyEvent;
} // namespace ui
namespace gfx {
class Image;
class Size;
} // namespace gfx
namespace keyboard {
class KeyboardUIController;
} // namespace keyboard
namespace ash {
class AshWebView;
class PhoneHubTray;
class TrayBubbleView;
class TrayBubbleWrapper;
class SessionControllerImpl;
class Shelf;
class Shell;
// This class represents the Eche tray button in the status area and
// controls the bubble that is shown when the tray button is clicked.
class ASH_EXPORT EcheTray
: public TrayBackgroundView,
public SessionObserver,
public ScreenLayoutObserver,
public ShelfObserver,
public SystemTrayObserver,
public display::DisplayObserver,
public KeyboardControllerObserver,
public ShellObserver,
public eche_app::EcheConnectionStatusHandler::Observer {
METADATA_HEADER(EcheTray, TrayBackgroundView)
public:
// TODO(b/226687249): Move to ash/webui/eche_app_ui if dependency cycle error
// is fixed. Enum representing the connection fail reason. These values are
// persisted to logs. Entries should not be renumbered and numeric values
// should never be reused. Keep in sync with the ConnectionFailReason UMA enum
// defined in //tools/metrics/histograms/enums.xml.
//
// LINT.IfChange(ConnectionFailReason)
enum class ConnectionFailReason {
// Initial state.
kUnknown = 0,
// Timeout because signaling no response, we don't received any response
// or request before timeout. Report this from EcheSignaler.
kSignalingNotTriggered = 1,
// Timeout because signaling response is late. Report this from
// EcheSignaler.
kSignalingHasLateResponse = 2,
// Timeout because we can't finish the whole connection process on time
// after receiving the signaling request from the remote device. Report
// this from EcheSignaler.
kSignalingHasLateRequest = 3,
// Timeout because the security channel disconnected. Report this from
// EcheSignaler.
kSecurityChannelDisconnected = 4,
// Connection fail because the device is in the tablet mode. Report this
// from EcheTray.
kConnectionFailInTabletMode = 5,
// Connection fail because the devices are on different networks. Report
// this from EcheTray.
kConnectionFailSsidDifferent = 6,
// Connection fail because the remote device is on cellular network. Report
// this from EcheTray.
kConnectionFailRemoteDeviceOnCellular = 7,
kMaxValue = kConnectionFailRemoteDeviceOnCellular,
};
// LINT.ThenChange(//tools/metrics/histograms/enums.xml:ConnectionFailReason)
using GracefulCloseCallback = base::OnceCallback<void()>;
using GracefulGoBackCallback = base::RepeatingCallback<void()>;
using BubbleShownCallback = base::RepeatingCallback<void(AshWebView* view)>;
explicit EcheTray(Shelf* shelf);
EcheTray(const EcheTray&) = delete;
EcheTray& operator=(const EcheTray&) = delete;
~EcheTray() override;
bool IsInitialized() const;
// TrayBackgroundView:
void ClickedOutsideBubble(const ui::LocatedEvent& event) override;
void UpdateTrayItemColor(bool is_active) override;
void HandleLocaleChange() override;
void HideBubbleWithView(const TrayBubbleView* bubble_view) override;
void AnchorUpdated() override;
void Initialize() override;
void CloseBubbleInternal() override;
void ShowBubble() override;
TrayBubbleView* GetBubbleView() override;
views::Widget* GetBubbleWidget() const override;
void OnVirtualKeyboardVisibilityChanged() override;
bool CacheBubbleViewForHide() const override;
// TrayBubbleView::Delegate:
std::u16string GetAccessibleNameForBubble() override;
bool ShouldEnableExtraKeyboardAccessibility() override;
void HideBubble(const TrayBubbleView* bubble_view) override;
// SessionObserver:
void OnLockStateChanged(bool locked) override;
// KeyboardControllerObserver:
void OnKeyboardUIDestroyed() override;
void OnKeyboardHidden(bool is_temporary_hide) override;
// eche_app::EcheConnectionStatusHandler::Observer:
void OnConnectionStatusChanged(
eche_app::mojom::ConnectionStatus connection_status) override;
void OnRequestBackgroundConnectionAttempt() override;
// SystemTrayObserver:
void OnFocusLeavingSystemTray(bool reverse) override {}
void OnStatusAreaAnchoredBubbleVisibilityChanged(TrayBubbleView* tray_bubble,
bool visible) override;
// Callback called when the eche icon or tray button is pressed.
void OnButtonPressed();
// Sets the url that will be passed to the webview.
// Setting a new value will cause the current bubble be destroyed.
void SetUrl(const GURL& url);
// Sets the icon that will be used on the tray.
void SetIcon(const gfx::Image& icon, const std::u16string& tooltip_text);
// Reduces the size of the original icon by the `offset`. Passing a zero
// `offset` will bring the icon back to its original size.
void ResizeIcon(int offset_dip);
// Sets graceful close callback function. When close Eche Bubble, it will
// notify to Eche Web to release connection resource. Be aware that once this
// is set, close button will not call PurgeAndClose() but rely on Eche Web to
// close window when connection resource is released; if it is not set, then
// it will immediately call PurgeAndClose() to close window.
void SetGracefulCloseCallback(GracefulCloseCallback graceful_close_callback);
// Sets graceful go back callback function. When users click the ArrowBack
// button in the Eche Bubble, `graceful_go_back_callback` will notify Eche
// web content to send the GoBack key event. Be aware that once this is set,
// the ArrowBack button will call `web_view.GoBack()` and run
// `graceful_go_back_callback` together and rely on Eche web content to send
// the GoBack key event to the server when the ArrowBack button is clicked; if
// this is not set, then the ArrowBack button will immediately call
// `web_view.GoBack()` to go back the previous page.
void SetGracefulGoBackCallback(
GracefulGoBackCallback graceful_go_back_callback);
// Sets a callback that runs when the bubble is shown for the first time, and
// returns the webview.
void SetBubbleShownCallback(BubbleShownCallback bubble_shown_callback);
views::Button* GetMinimizeButtonForTesting() const;
views::Button* GetCloseButtonForTesting() const;
views::Button* GetArrowBackButtonForTesting() const;
// Initializes the bubble with given parameters. If there is any previous
// bubble already shown with a different URL it is going to be closed. The
// bubble is not shown initially until `ShowBubble` is called.
// The `url` parameter is used to load the `WebView` inside the bubble.
// The `icon` is used to update the tray icon for `EcheTray`.
// The `visible_name` is shown as a tooltip for the Eche icon.
//
// Returns true if the bubble is loaded or initialized successfully.
bool LoadBubble(const GURL& url,
const gfx::Image& icon,
const std::u16string& visible_name,
const std::u16string& phone_name,
eche_app::mojom::ConnectionStatus last_connection_status,
eche_app::mojom::AppStreamLaunchEntryPoint entry_point);
// Destroys the view inclusing the web view.
// Note: `CloseBubble` only hides the view.
void PurgeAndClose();
void HideBubble();
// Receives the `status` change when the video streaming is started or
// stopped. Controls the bubble widget based on the different `status`
// changes. There are two cases: 1. Shows the bubble when the streaming is
// started. 2. Purges and closes the bubble when the streaming is stopped.
void OnStreamStatusChanged(eche_app::mojom::StreamStatus status);
// Receives the `orientation` change when the stream switches between
// landscape and portrait.
void OnStreamOrientationChanged(bool is_landscape);
// Set up the params and init the bubble.
// Note: This function makes the bubble active and makes the
// TrayBackgroundView's background inkdrop activate.
void InitBubble(const std::u16string& phone_name,
eche_app::mojom::ConnectionStatus last_connection_status,
eche_app::mojom::AppStreamLaunchEntryPoint entry_point);
// Starts graceful close to ensure the connection resource is released before
// the window is closed.
void StartGracefulClose();
void OnBackgroundConnectionTimeout();
void SetEcheConnectionStatusHandler(
eche_app::EcheConnectionStatusHandler* eche_connection_status_handler);
bool IsBackgroundConnectionAttemptInProgress();
// Test helpers
bool get_is_landscape_for_test() { return is_landscape_; }
TrayBubbleWrapper* get_bubble_wrapper_for_test() { return bubble_.get(); }
AshWebView* get_web_view_for_test() { return web_view_; }
AshWebView* get_initializer_webview_for_test() {
return initializer_webview_.get();
}
views::ImageButton* GetIcon();
std::u16string GetAccessibleName();
private:
FRIEND_TEST_ALL_PREFIXES(EcheTrayTest, EcheTrayCreatesBubbleButHideFirst);
FRIEND_TEST_ALL_PREFIXES(EcheTrayTest, EcheTrayOnDisplayConfigurationChanged);
FRIEND_TEST_ALL_PREFIXES(EcheTrayTest,
EcheTrayKeyboardShowHideUpdateBubbleBounds);
FRIEND_TEST_ALL_PREFIXES(EcheTrayTest, EcheTrayOnStreamOrientationChanged);
// Intercepts all the events targeted to the internal webview in order to
// process the accelerator keys.
class EventInterceptor : public ui::EventHandler {
public:
explicit EventInterceptor(EcheTray* eche_tray);
EventInterceptor(const EventInterceptor&) = delete;
EventInterceptor& operator=(const EventInterceptor&) = delete;
~EventInterceptor() override;
// ui::EventHandler:
void OnKeyEvent(ui::KeyEvent* event) override;
private:
const raw_ptr<EcheTray> eche_tray_;
};
// Calculates and returns the size of the Exo bubble based on the screen size
// and orientation.
gfx::Size CalculateSizeForEche() const;
// Handles the click on the "back" arrow in the header.
void OnArrowBackActivated();
// Creates the header of the bubble that includes a back arrow,
// close, and minimize buttons.
std::unique_ptr<views::View> CreateBubbleHeaderView(
const std::u16string& phone_name);
void StopLoadingAnimation();
void StartLoadingAnimation();
void SetIconVisibility(bool visibility);
PhoneHubTray* GetPhoneHubTray();
EcheIconLoadingIndicatorView* GetLoadingIndicator();
// Resize Eche size and update the bubble's position.
void UpdateEcheSizeAndBubbleBounds();
// ScreenLayoutObserver:
void OnDidApplyDisplayChanges() override;
// ShelfObserver:
void OnAutoHideStateChanged(ShelfAutoHideState new_state) override;
// display::DisplayObserver:
void OnDisplayTabletStateChanged(display::TabletState state) override;
// ShellObserver:
void OnShelfAlignmentChanged(aura::Window* root_window,
ShelfAlignment old_alignment) override;
// Called when the display tablet state is changed to kInTabletMode.
void OnTabletModeStarted();
// Processes the accelerator keys and returns true if the accelerator was
// processed completely in this method and no further processing is needed.
bool ProcessAcceleratorKeys(ui::KeyEvent* event);
// Returns true only if the bubble is initialized and visible.
bool IsBubbleVisible();
// Starts graceful shutdown for the initializer.
void StartGracefulCloseInitializer();
// Kills the renderer.
void CloseInitializer();
// The url that is transferred to the web view.
// In the current implementation, this is supposed to be
// Eche window URL. However, the bubble does not interpret,
// validate, or expect a special url format or page behabvior.
GURL url_;
// Icon of the tray. Unowned.
const raw_ptr<views::ImageView> icon_;
// The bubble that appears after clicking the tray button.
std::unique_ptr<TrayBubbleWrapper> bubble_;
// The webview shown in the bubble that contains the Eche SWA.
// owned by `bubble_`
raw_ptr<AshWebView> web_view_ = nullptr;
// Webview used to create a prewarming channel, before we have a video to
// attach to.
std::unique_ptr<AshWebView> initializer_webview_{};
std::unique_ptr<base::DelayTimer> initializer_timeout_{};
base::OnceClosure on_initializer_closed_;
bool has_reported_initializer_result_ = false;
bool has_retried_initializer_ = false;
raw_ptr<eche_app::EcheConnectionStatusHandler>
eche_connection_status_handler_ = nullptr;
GracefulCloseCallback graceful_close_callback_;
GracefulGoBackCallback graceful_go_back_callback_;
BubbleShownCallback bubble_shown_callback_;
// The unload timer to force close EcheTray in case unload error.
std::unique_ptr<base::DelayTimer> unload_timer_;
raw_ptr<views::View, DanglingUntriaged> header_view_ = nullptr;
raw_ptr<views::Button> close_button_ = nullptr;
raw_ptr<views::Button> minimize_button_ = nullptr;
raw_ptr<views::Button> arrow_back_button_ = nullptr;
std::unique_ptr<EventInterceptor> event_interceptor_;
// The time a stream is initializing. Used to record the elapsed time from
// when the stream is initializing to when the stream is closed by user.
std::optional<base::TimeTicks> init_stream_timestamp_;
// The orientation of the stream (portrait vs landscape). The default
// orientation is portrait.
bool is_landscape_ = false;
bool is_stream_started_ = false;
std::u16string phone_name_;
// Observers
base::ScopedObservation<SessionControllerImpl, SessionObserver>
observed_session_{this};
base::ScopedObservation<Shelf, ShelfObserver> shelf_observation_{this};
base::ScopedObservation<Shell, ShellObserver> shell_observer_{this};
base::ScopedObservation<keyboard::KeyboardUIController,
KeyboardControllerObserver>
keyboard_observation_{this};
display::ScopedDisplayObserver display_observer_{this};
base::WeakPtrFactory<EcheTray> weak_factory_{this};
};
} // namespace ash
#endif // ASH_SYSTEM_ECHE_ECHE_TRAY_H_