[eche] When ctrl+c and ctrl+v is pressed show a toast that copy paste
operation is not yet supported. Also, show a test when we switch to tablet mode. Background: Right now, Eche does not support copy and paste operations. In our UX research study it turned out that it can cause confusion and negative impact on the users. We have plans to implement the copy-paste operation in q3/q4. However, in the meantime we decided to show a toast to let the user know that it is not a bug and we are going to implement this. Note: there are some trailing whitespaces in the ash_strings that I removed, hence setting Skip-Translation-Screenshots-Check. Bug: b/232818143, b/236681261 Change-Id: I85d335cba3764b1ee7e92d2cd1321ad283572737 Skip-Translation-Screenshots-Check: True Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3652620 Reviewed-by: Alex Newcomer <newcomer@chromium.org> Reviewed-by: Zentaro Kavanagh <zentaro@chromium.org> Commit-Queue: Ahmed Fakhry <afakhry@chromium.org> Commit-Queue: Abbas Nayebi <nayebi@google.com> Auto-Submit: Abbas Nayebi <nayebi@google.com> Reviewed-by: Ahmed Fakhry <afakhry@chromium.org> Cr-Commit-Position: refs/heads/main@{#1016969}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
722884891e
commit
58e44fc836
@ -5037,6 +5037,15 @@ New install
|
||||
<message name="IDS_ASH_STATUS_TRAY_KEYBOARD_BACKLIGHT_ACCESSIBLE_NAME" desc="The accessible text for the brightness slider keyboard backlight icon.">
|
||||
Keyboard backlight color
|
||||
</message>
|
||||
|
||||
<!-- Eche feature -->
|
||||
<message name="IDS_ASH_ECHE_TOAST_COPY_PASTE_NOT_IMPLEMENTED" desc="A toast message that we show when user tries to use ctrl+c or ctrl+v in the Eche window.">
|
||||
Can't copy or paste content at this time
|
||||
</message>
|
||||
<message name="IDS_ASH_ECHE_TOAST_TABLET_MODE_NOT_SUPPORTED" desc="A toast message that we show when user tries to switch to tablet mode.">
|
||||
Can't stream apps in tablet mode. Try again in laptop mode.
|
||||
</message>
|
||||
|
||||
</messages>
|
||||
</release>
|
||||
</grit>
|
||||
|
@ -0,0 +1 @@
|
||||
12f71e794adad361f0ba66821795eb99d885ea23
|
@ -0,0 +1 @@
|
||||
e39f35d769f36adaf23fcc7bf77743ddf4cac106
|
@ -197,7 +197,9 @@ enum class ToastCatalogName {
|
||||
kUndoCloseAll = 34,
|
||||
kEcheAppToast = 35,
|
||||
kDeprecateAssistantStylus = 36,
|
||||
kMaxValue = kDeprecateAssistantStylus,
|
||||
kEcheTrayCopyPasteNotImplemented = 37,
|
||||
kEcheTrayTabletModeNotSupported = 38,
|
||||
kMaxValue = kEcheTrayTabletModeNotSupported,
|
||||
};
|
||||
|
||||
} // namespace ash
|
||||
|
@ -8,12 +8,16 @@
|
||||
|
||||
#include "ash/accessibility/accessibility_controller_impl.h"
|
||||
#include "ash/components/multidevice/logging/logging.h"
|
||||
#include "ash/constants/notifier_catalogs.h"
|
||||
#include "ash/keyboard/ui/keyboard_ui_controller.h"
|
||||
#include "ash/public/cpp/accelerators.h"
|
||||
#include "ash/public/cpp/ash_web_view.h"
|
||||
#include "ash/public/cpp/ash_web_view_factory.h"
|
||||
#include "ash/public/cpp/keyboard/keyboard_controller.h"
|
||||
#include "ash/public/cpp/shell_window_ids.h"
|
||||
#include "ash/public/cpp/system/toast_data.h"
|
||||
#include "ash/public/cpp/system/toast_manager.h"
|
||||
#include "ash/public/cpp/tablet_mode_observer.h"
|
||||
#include "ash/resources/vector_icons/vector_icons.h"
|
||||
#include "ash/root_window_controller.h"
|
||||
#include "ash/session/session_controller_impl.h"
|
||||
@ -39,10 +43,13 @@
|
||||
#include "base/time/time.h"
|
||||
#include "components/account_id/account_id.h"
|
||||
#include "components/vector_icons/vector_icons.h"
|
||||
#include "ui/base/accelerators/accelerator.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
#include "ui/base/metadata/metadata_impl_macros.h"
|
||||
#include "ui/compositor/layer.h"
|
||||
#include "ui/events/event.h"
|
||||
#include "ui/events/event_constants.h"
|
||||
#include "ui/events/types/event_type.h"
|
||||
#include "ui/gfx/geometry/insets.h"
|
||||
#include "ui/gfx/geometry/rect.h"
|
||||
#include "ui/gfx/geometry/size.h"
|
||||
@ -94,6 +101,13 @@ constexpr float kMaxHeightPercentage = 0.85;
|
||||
// Unload timeout to close Eche Bubble in case error from Ech web during closing
|
||||
constexpr base::TimeDelta kUnloadTimeoutDuration = base::Milliseconds(500);
|
||||
|
||||
// The ID for the "Copy/paste not yet implemented" toast.
|
||||
constexpr char kEcheTrayCopyPasteNotImplementedToastId[] =
|
||||
"eche_tray_toast_ids.copy_paste_not_implemented";
|
||||
// The ID for the "Tablet mode not supported" toast.
|
||||
constexpr char kEcheTrayTabletModeNotSupportedId[] =
|
||||
"eche_tray_toast_ids.tablet_mode_not_supported";
|
||||
|
||||
// Creates a button with the given callback, icon, and tooltip text.
|
||||
// `message_id` is the resource id of the tooltip text of the icon.
|
||||
std::unique_ptr<views::Button> CreateButton(
|
||||
@ -121,12 +135,7 @@ EcheTray::EventInterceptor::EventInterceptor(EcheTray* eche_tray)
|
||||
EcheTray::EventInterceptor::~EventInterceptor() = default;
|
||||
|
||||
void EcheTray::EventInterceptor::OnKeyEvent(ui::KeyEvent* event) {
|
||||
// To provide consistent behavior with a menu, process accelerator as a menu
|
||||
// is open if the event is not handled by the widget.
|
||||
ui::Accelerator accelerator(*event);
|
||||
if (AcceleratorController::Get()->DoesAcceleratorMatchAction(
|
||||
accelerator, AcceleratorAction::WINDOW_MINIMIZE)) {
|
||||
eche_tray_->CloseBubble();
|
||||
if (eche_tray_->ProcessAcceleratorKeys(event)) {
|
||||
event->StopPropagation();
|
||||
return;
|
||||
}
|
||||
@ -146,6 +155,10 @@ EcheTray::EcheTray(Shelf* shelf)
|
||||
// Note: `ScreenLayoutObserver` starts observing at its constructor.
|
||||
observed_session_.Observe(Shell::Get()->session_controller());
|
||||
icon_->SetTooltipText(GetAccessibleNameForTray());
|
||||
icon_->SetImage(CreateVectorIcon(
|
||||
kPhoneHubPhoneIcon,
|
||||
AshColorProvider::Get()->GetContentLayerColor(
|
||||
AshColorProvider::ContentLayerType::kIconColorPrimary)));
|
||||
shelf_observation_.Observe(shelf);
|
||||
tablet_mode_observation_.Observe(Shell::Get()->tablet_mode_controller());
|
||||
shell_observer_.Observe(Shell::Get());
|
||||
@ -227,7 +240,7 @@ void EcheTray::ShowBubble() {
|
||||
|
||||
bool EcheTray::PerformAction(const ui::Event& event) {
|
||||
// Simply toggle between visible/invisibvle
|
||||
if (bubble_ && bubble_->bubble_view()->GetVisible()) {
|
||||
if (IsBubbleVisible()) {
|
||||
HideBubble();
|
||||
} else {
|
||||
#ifdef FAKE_BUBBLE_FOR_DEBUG
|
||||
@ -259,7 +272,7 @@ void EcheTray::OnAnyBubbleVisibilityChanged(views::Widget* bubble_widget,
|
||||
return;
|
||||
|
||||
// Another bubble has become visible, so minimize this one.
|
||||
if (visible && bubble_->bubble_view()->GetVisible())
|
||||
if (visible && IsBubbleVisible())
|
||||
HideBubble();
|
||||
}
|
||||
|
||||
@ -300,13 +313,13 @@ void EcheTray::OnLockStateChanged(bool locked) {
|
||||
}
|
||||
|
||||
void EcheTray::OnKeyboardUIDestroyed() {
|
||||
if (!bubble_ || !bubble_->bubble_view()->GetVisible())
|
||||
if (!IsBubbleVisible())
|
||||
return;
|
||||
UpdateBubbleBounds();
|
||||
}
|
||||
|
||||
void EcheTray::OnKeyboardVisibilityChanged(bool visible) {
|
||||
if (visible || !bubble_ || !bubble_->bubble_view()->GetVisible())
|
||||
if (visible || !IsBubbleVisible())
|
||||
return;
|
||||
UpdateBubbleBounds();
|
||||
}
|
||||
@ -331,16 +344,26 @@ void EcheTray::SetIcon(const gfx::Image& icon,
|
||||
}
|
||||
}
|
||||
|
||||
void EcheTray::LoadBubble(const GURL& url,
|
||||
bool EcheTray::LoadBubble(const GURL& url,
|
||||
const gfx::Image& icon,
|
||||
const std::u16string& visible_name) {
|
||||
if (Shell::Get()->IsInTabletMode()) {
|
||||
ash::ToastManager::Get()->Show(ash::ToastData(
|
||||
kEcheTrayTabletModeNotSupportedId,
|
||||
ash::ToastCatalogName::kEcheTrayTabletModeNotSupported,
|
||||
l10n_util::GetStringUTF16(IDS_ASH_ECHE_TOAST_TABLET_MODE_NOT_SUPPORTED),
|
||||
ash::ToastData::kDefaultToastDuration,
|
||||
/*visible_on_lock_screen=*/false));
|
||||
PA_LOG(WARNING) << "Eche load failed due to tablet mode.";
|
||||
return false;
|
||||
}
|
||||
SetUrl(url);
|
||||
SetIcon(icon, /*tooltip_text=*/visible_name);
|
||||
// If the bubble is already initialized, setting the icon and url was enough
|
||||
// to navigate the bubble to the new address.
|
||||
if (IsInitialized()) {
|
||||
ShowBubble();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
InitBubble();
|
||||
StartLoadingAnimation();
|
||||
@ -351,6 +374,7 @@ void EcheTray::LoadBubble(const GURL& url,
|
||||
}
|
||||
// Hide bubble first until the streaming is ready.
|
||||
HideBubble();
|
||||
return true;
|
||||
}
|
||||
|
||||
void EcheTray::PurgeAndClose() {
|
||||
@ -615,7 +639,7 @@ EcheIconLoadingIndicatorView* EcheTray::GetLoadingIndicator() {
|
||||
}
|
||||
|
||||
void EcheTray::UpdateBubbleBounds() {
|
||||
if (!bubble_)
|
||||
if (!bubble_ || !bubble_->GetBubbleView())
|
||||
return;
|
||||
bubble_->GetBubbleView()->ChangeAnchorRect(GetAnchor());
|
||||
}
|
||||
@ -633,7 +657,15 @@ void EcheTray::OnShelfIconPositionsChanged() {
|
||||
}
|
||||
|
||||
void EcheTray::OnTabletModeStarted() {
|
||||
UpdateBubbleBounds();
|
||||
if (!IsBubbleVisible())
|
||||
return;
|
||||
ash::ToastManager::Get()->Show(ash::ToastData(
|
||||
kEcheTrayTabletModeNotSupportedId,
|
||||
ash::ToastCatalogName::kEcheTrayTabletModeNotSupported,
|
||||
l10n_util::GetStringUTF16(IDS_ASH_ECHE_TOAST_TABLET_MODE_NOT_SUPPORTED),
|
||||
ash::ToastData::kDefaultToastDuration,
|
||||
/*visible_on_lock_screen=*/false));
|
||||
PurgeAndClose();
|
||||
}
|
||||
|
||||
void EcheTray::OnTabletModeEnded() {
|
||||
@ -648,6 +680,74 @@ gfx::Rect EcheTray::GetAnchor() {
|
||||
return shelf()->GetSystemTrayAnchorRect();
|
||||
}
|
||||
|
||||
// TODO(b/234848974): Try to use View::AddAccelerator for the bubble view
|
||||
// and then add the handler in View::AcceleratorPressed.
|
||||
bool EcheTray::ProcessAcceleratorKeys(ui::KeyEvent* event) {
|
||||
ui::Accelerator accelerator(*event);
|
||||
|
||||
auto* accelerator_controller = AcceleratorController::Get();
|
||||
|
||||
// Process minimize action
|
||||
// Please note that the bubble is not a normal window and it has a special
|
||||
// minimize behavior that is closer to hide than real minimize.
|
||||
//
|
||||
// TODO(https://crbug/1338650): See if we can just leave this to be handled
|
||||
// upper in the chain and perform the minimize by reacting to
|
||||
// ToggleMinimized().
|
||||
if (accelerator_controller->DoesAcceleratorMatchAction(
|
||||
accelerator, AcceleratorAction::WINDOW_MINIMIZE)) {
|
||||
CloseBubble();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (accelerator_controller->DoesAcceleratorMatchAction(
|
||||
accelerator, AcceleratorAction::OPEN_FEEDBACK_PAGE) ||
|
||||
accelerator_controller->DoesAcceleratorMatchAction(
|
||||
accelerator, AcceleratorAction::EXIT)) {
|
||||
views::ViewsDelegate::GetInstance()->ProcessAcceleratorWhileMenuShowing(
|
||||
accelerator);
|
||||
event->StopPropagation();
|
||||
return true;
|
||||
}
|
||||
|
||||
const ui::KeyboardCode key_code = event->key_code();
|
||||
const bool is_only_control_down = ui::Accelerator::MaskOutKeyEventFlags(
|
||||
event->flags()) == ui::EF_CONTROL_DOWN;
|
||||
|
||||
if (event->type() == ui::ET_KEY_PRESSED && is_only_control_down) {
|
||||
switch (key_code) {
|
||||
case ui::VKEY_C:
|
||||
case ui::VKEY_V:
|
||||
case ui::VKEY_X:
|
||||
ash::ToastManager::Get()->Show(ash::ToastData(
|
||||
kEcheTrayCopyPasteNotImplementedToastId,
|
||||
ash::ToastCatalogName::kEcheTrayCopyPasteNotImplemented,
|
||||
l10n_util::GetStringUTF16(
|
||||
IDS_ASH_ECHE_TOAST_COPY_PASTE_NOT_IMPLEMENTED),
|
||||
ash::ToastData::kDefaultToastDuration,
|
||||
/*visible_on_lock_screen=*/false));
|
||||
return true;
|
||||
case ui::VKEY_W:
|
||||
// Please note that ctrl+w does not have a global accelerator action
|
||||
// similar to AcceleratorAction::WINDOW_MINIMIZE that was used above.
|
||||
//
|
||||
// TODO(https://crbug/1338650): See if we can just leave this to be
|
||||
// handled upper in the chain.
|
||||
StartGracefulClose();
|
||||
return true;
|
||||
default:
|
||||
// Do nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EcheTray::IsBubbleVisible() {
|
||||
return bubble_ && bubble_->GetBubbleView() &&
|
||||
bubble_->GetBubbleView()->GetVisible();
|
||||
}
|
||||
|
||||
BEGIN_METADATA(EcheTray, TrayBackgroundView)
|
||||
END_METADATA
|
||||
|
||||
|
@ -146,7 +146,9 @@ class ASH_EXPORT EcheTray : public TrayBackgroundView,
|
||||
// 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.
|
||||
void LoadBubble(const GURL& url,
|
||||
//
|
||||
// Returns true if the bubble is loaded or initialized successfully.
|
||||
bool LoadBubble(const GURL& url,
|
||||
const gfx::Image& icon,
|
||||
const std::u16string& visible_name);
|
||||
|
||||
@ -236,6 +238,13 @@ class ASH_EXPORT EcheTray : public TrayBackgroundView,
|
||||
// returns the position of the anchor that bubble needs to be anchored to.
|
||||
gfx::Rect GetAnchor();
|
||||
|
||||
// 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();
|
||||
|
||||
// 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,
|
||||
|
@ -7,13 +7,17 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "ash/public/cpp/system/toast_manager.h"
|
||||
#include "ash/shell.h"
|
||||
#include "ash/strings/grit/ash_strings.h"
|
||||
#include "ash/system/phonehub/phone_hub_tray.h"
|
||||
#include "ash/system/status_area_widget_test_helper.h"
|
||||
#include "ash/system/toast/toast_manager_impl.h"
|
||||
#include "ash/system/tray/tray_bubble_wrapper.h"
|
||||
#include "ash/test/ash_test_base.h"
|
||||
#include "ash/test/test_ash_web_view_factory.h"
|
||||
#include "base/test/scoped_feature_list.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
#include "ui/display/test/display_manager_test_api.h"
|
||||
#include "ui/events/event_constants.h"
|
||||
#include "ui/events/keycodes/keyboard_codes_posix.h"
|
||||
@ -81,6 +85,8 @@ class EcheTrayTest : public AshTestBase {
|
||||
|
||||
display::test::DisplayManagerTestApi(display_manager())
|
||||
.SetFirstDisplayAsInternalDisplay();
|
||||
|
||||
toast_manager_ = Shell::Get()->toast_manager();
|
||||
}
|
||||
|
||||
// Performs a tap on the eche tray button.
|
||||
@ -96,11 +102,13 @@ class EcheTrayTest : public AshTestBase {
|
||||
|
||||
EcheTray* eche_tray() { return eche_tray_; }
|
||||
PhoneHubTray* phone_hub_tray() { return phone_hub_tray_; }
|
||||
ToastManagerImpl* toast_manager() { return toast_manager_; }
|
||||
|
||||
private:
|
||||
EcheTray* eche_tray_ = nullptr; // Not owned
|
||||
PhoneHubTray* phone_hub_tray_ = nullptr; // Not owned
|
||||
base::test::ScopedFeatureList feature_list_;
|
||||
ToastManagerImpl* toast_manager_ = nullptr;
|
||||
|
||||
// Calling the factory constructor is enough to set it up.
|
||||
std::unique_ptr<TestAshWebViewFactory> test_web_view_factory_ =
|
||||
@ -362,4 +370,75 @@ TEST_F(EcheTrayTest, AcceleratorKeyHandled_Minimize) {
|
||||
EXPECT_FALSE(is_web_content_unloaded_);
|
||||
}
|
||||
|
||||
TEST_F(EcheTrayTest, AcceleratorKeyHandled_Ctrl_W) {
|
||||
ResetUnloadWebContent();
|
||||
eche_tray()->SetGracefulCloseCallback(base::BindOnce(&UnloadWebContent));
|
||||
eche_tray()->LoadBubble(GURL("http://google.com"), CreateTestImage(),
|
||||
u"app 1");
|
||||
eche_tray()->ShowBubble();
|
||||
|
||||
EXPECT_TRUE(
|
||||
eche_tray()->get_bubble_wrapper_for_test()->bubble_view()->GetVisible());
|
||||
|
||||
// Now press the ctrl+w that closes the bubble.
|
||||
GetEventGenerator()->PressKey(ui::KeyboardCode::VKEY_W, ui::EF_CONTROL_DOWN);
|
||||
|
||||
// Check to see if the bubble is closed and purged.
|
||||
EXPECT_TRUE(is_web_content_unloaded_);
|
||||
}
|
||||
|
||||
TEST_F(EcheTrayTest, AcceleratorKeyHandled_Ctrl_C) {
|
||||
eche_tray()->LoadBubble(GURL("http://google.com"), CreateTestImage(),
|
||||
u"app 1");
|
||||
eche_tray()->ShowBubble();
|
||||
|
||||
EXPECT_TRUE(
|
||||
eche_tray()->get_bubble_wrapper_for_test()->bubble_view()->GetVisible());
|
||||
EXPECT_FALSE(toast_manager()->IsRunning(
|
||||
"eche_tray_toast_ids.copy_paste_not_implemented"));
|
||||
|
||||
// Now press the ctrl+w that closes the bubble.
|
||||
GetEventGenerator()->PressKey(ui::KeyboardCode::VKEY_C, ui::EF_CONTROL_DOWN);
|
||||
|
||||
// Check to see if a toast is shown
|
||||
EXPECT_TRUE(toast_manager()->IsRunning(
|
||||
"eche_tray_toast_ids.copy_paste_not_implemented"));
|
||||
}
|
||||
|
||||
TEST_F(EcheTrayTest, AcceleratorKeyHandled_Ctrl_V) {
|
||||
eche_tray()->LoadBubble(GURL("http://google.com"), CreateTestImage(),
|
||||
u"app 1");
|
||||
eche_tray()->ShowBubble();
|
||||
|
||||
EXPECT_TRUE(
|
||||
eche_tray()->get_bubble_wrapper_for_test()->bubble_view()->GetVisible());
|
||||
EXPECT_FALSE(toast_manager()->IsRunning(
|
||||
"eche_tray_toast_ids.copy_paste_not_implemented"));
|
||||
|
||||
// Now press the ctrl+w that closes the bubble.
|
||||
GetEventGenerator()->PressKey(ui::KeyboardCode::VKEY_V, ui::EF_CONTROL_DOWN);
|
||||
|
||||
// Check to see if a toast is shown
|
||||
EXPECT_TRUE(toast_manager()->IsRunning(
|
||||
"eche_tray_toast_ids.copy_paste_not_implemented"));
|
||||
}
|
||||
|
||||
TEST_F(EcheTrayTest, AcceleratorKeyHandled_Ctrl_X) {
|
||||
eche_tray()->LoadBubble(GURL("http://google.com"), CreateTestImage(),
|
||||
u"app 1");
|
||||
eche_tray()->ShowBubble();
|
||||
|
||||
EXPECT_TRUE(
|
||||
eche_tray()->get_bubble_wrapper_for_test()->bubble_view()->GetVisible());
|
||||
EXPECT_FALSE(toast_manager()->IsRunning(
|
||||
"eche_tray_toast_ids.copy_paste_not_implemented"));
|
||||
|
||||
// Now press the ctrl+w that closes the bubble.
|
||||
GetEventGenerator()->PressKey(ui::KeyboardCode::VKEY_X, ui::EF_CONTROL_DOWN);
|
||||
|
||||
// Check to see if a toast is shown
|
||||
EXPECT_TRUE(toast_manager()->IsRunning(
|
||||
"eche_tray_toast_ids.copy_paste_not_implemented"));
|
||||
}
|
||||
|
||||
} // namespace ash
|
||||
|
Reference in New Issue
Block a user