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
|
// 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_
|