blob: f32f80f60435b894a585c0f5d13c5ccdfd19f2eb [file] [log] [blame]
// Copyright 2022 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package arc
import (
"context"
"time"
"chromiumos/tast/common/android/adb"
"chromiumos/tast/common/android/ui"
"chromiumos/tast/ctxutil"
"chromiumos/tast/errors"
"chromiumos/tast/local/arc"
"chromiumos/tast/local/bundles/cros/arc/wm"
"chromiumos/tast/local/chrome"
"chromiumos/tast/local/chrome/ash"
"chromiumos/tast/local/chrome/display"
"chromiumos/tast/local/chrome/uiauto"
"chromiumos/tast/local/chrome/uiauto/pointer"
"chromiumos/tast/local/coords"
"chromiumos/tast/local/input"
"chromiumos/tast/local/power"
ashwallpaper "chromiumos/tast/local/wallpaper"
ashwallpaperconstants "chromiumos/tast/local/wallpaper/constants"
"chromiumos/tast/testing"
)
func init() {
testing.AddTest(&testing.Test{
Func: CompatSnap,
LacrosStatus: testing.LacrosVariantUnneeded,
Desc: "Tests compatible snapping works properly for resize-locked ARC apps",
Contacts: []string{"toshikikikuchi@chromium.org", "arc-framework+tast@google.com"},
Attr: []string{"group:mainline", "informational"},
SoftwareDeps: []string{"chrome", "android_vm"},
Timeout: 4 * time.Minute,
// TODO(b/215063759): Replace this with arcBootedInClamshellMode after the feature is launched.
Fixture: "arcBootedInClamshellModeWithCompatSnap",
})
}
func windowDragPoint(ctx context.Context, tconn *chrome.TestConn, act *arc.Activity) (coords.Point, error) {
window, err := ash.GetARCAppWindowInfo(ctx, tconn, act.PackageName())
if err != nil {
return coords.NewPoint(0, 0), errors.Wrap(err, "failed to get window info")
}
// As resize-locked windows have the compat mode button at the center of the caption, we need to drag on the right of the back button instead of the center point.
return coords.NewPoint(window.BoundsInRoot.Left+100, window.BoundsInRoot.Top+window.CaptionHeight/2), nil
}
func waitForWindowState(ctx context.Context, tconn *chrome.TestConn, d *ui.Device, act *arc.Activity, arcWindowState arc.WindowState) error {
ashWindowState, err := arcWindowState.ToAshWindowState()
if err != nil {
return errors.Wrap(err, "failed to convert arc window state to ash window state")
}
if err := d.WaitForIdle(ctx, 10*time.Second); err != nil {
return errors.Wrap(err, "failed to wait for Android to be idle")
}
window, err := ash.GetARCAppWindowInfo(ctx, tconn, act.PackageName())
if err != nil {
return errors.Wrap(err, "failed to get window info")
}
if err := ash.WaitWindowFinishAnimating(ctx, tconn, window.ID); err != nil {
return errors.Wrap(err, "failed to wait for the window animation")
}
if err := ash.WaitForARCAppWindowState(ctx, tconn, act.PackageName(), ashWindowState); err != nil {
return errors.Wrapf(err, "failed to wait for ash-side window state: want %v", ashWindowState)
}
if err := testing.Poll(ctx, func(ctx context.Context) error {
actualArcWindowState, err := act.GetWindowState(ctx)
if err != nil {
return testing.PollBreak(errors.Wrap(err, "could not get ARC window state"))
}
if actualArcWindowState != arcWindowState {
return errors.Errorf("unexpected ARC window state: got %v; want %v", actualArcWindowState, arcWindowState)
}
return nil
}, &testing.PollOptions{Timeout: 10 * time.Second}); err != nil {
return errors.Wrap(err, "timed out waiting for ARC window state transition")
}
return nil
}
func checkCompatSnappedWindowState(ctx context.Context, tconn *chrome.TestConn, a *arc.ARC, cr *chrome.Chrome, d *ui.Device, act *arc.Activity, primary bool, stableWidth int) error {
snappedArcWindowState := arc.WindowStateSecondarySnapped
if primary {
snappedArcWindowState = arc.WindowStatePrimarySnapped
}
if err := waitForWindowState(ctx, tconn, d, act, snappedArcWindowState); err != nil {
return errors.Wrap(err, "failed to wait until window state changes to snapped")
}
window, err := ash.GetARCAppWindowInfo(ctx, tconn, act.PackageName())
if err != nil {
return errors.Wrap(err, "failed to get window info")
}
snappedWidth := window.BoundsInRoot.Width
if stableWidth != snappedWidth {
return errors.Wrapf(err, "incorrect compat-snapped window width: got %v; want %v", snappedWidth, stableWidth)
}
if err := wm.CheckResizeLockState(ctx, tconn, a, d, cr, act, wm.PhoneResizeLockMode, false /* isSplashVisible */); err != nil {
return errors.Wrap(err, "failed to verify the resize lock state after snapping")
}
return nil
}
func testUnsnapByDragging(ctx context.Context, tconn *chrome.TestConn, a *arc.ARC, cr *chrome.Chrome, pc pointer.Context, displayInfo *display.Info, d *ui.Device, act *arc.Activity, stableWidth int) error {
dragPoint, err := windowDragPoint(ctx, tconn, act)
if err != nil {
return errors.Wrap(err, "failed to get window drag point")
}
if err := pc.Drag(
dragPoint,
pc.DragTo(displayInfo.Bounds.CenterPoint(), 2*time.Second))(ctx); err != nil {
return errors.Wrap(err, "failed to drag to unsnap")
}
if err := waitForWindowState(ctx, tconn, d, act, arc.WindowStateNormal); err != nil {
return errors.Wrap(err, "failed to wait until window state changes to normal")
}
window, err := ash.GetARCAppWindowInfo(ctx, tconn, act.PackageName())
if err != nil {
return errors.Wrap(err, "failed to get window info")
}
snappedWidth := window.BoundsInRoot.Width
if stableWidth != snappedWidth {
return errors.Wrapf(err, "incorrect compat-snapped window width: got %v; want %v", snappedWidth, stableWidth)
}
if err := wm.CheckResizeLockState(ctx, tconn, a, d, cr, act, wm.PhoneResizeLockMode, false /* isSplashVisible */); err != nil {
return errors.Wrap(err, "failed to verify the resize lock state of compat-snapped window")
}
return nil
}
func testSnapFromOverview(ctx context.Context, tconn *chrome.TestConn, a *arc.ARC, cr *chrome.Chrome, pc pointer.Context, displayInfo *display.Info, d *ui.Device, act *arc.Activity, primary bool, stableWidth int) error {
if err := ash.SetOverviewModeAndWait(ctx, tconn, true); err != nil {
return errors.Wrap(err, "failed to enter overview")
}
w, err := ash.FindFirstWindowInOverview(ctx, tconn)
if err != nil {
return errors.Wrap(err, "failed to find window in overview grid")
}
displayEdgeMargin := 20
snapDestinationX := displayInfo.Bounds.Width - displayEdgeMargin
if primary {
snapDestinationX = displayEdgeMargin
}
if err := pc.Drag(
w.OverviewInfo.Bounds.CenterPoint(),
pc.DragTo(coords.NewPoint(snapDestinationX, displayInfo.Bounds.Height/2), 2*time.Second))(ctx); err != nil {
return errors.Wrap(err, "failed to drag to snap from overview")
}
if err := checkCompatSnappedWindowState(ctx, tconn, a, cr, d, act, primary, stableWidth); err != nil {
return errors.Wrap(err, "failed to wait until window state change")
}
return nil
}
func testSnapByDragToSnap(ctx context.Context, tconn *chrome.TestConn, a *arc.ARC, cr *chrome.Chrome, pc pointer.Context, displayInfo *display.Info, d *ui.Device, act *arc.Activity, primary bool, stableWidth int) error {
snapDestinationX := displayInfo.Bounds.Width
if primary {
snapDestinationX = 0
}
dragPoint, err := windowDragPoint(ctx, tconn, act)
if err != nil {
return errors.Wrap(err, "failed to get window drag point")
}
if err := pc.Drag(
dragPoint,
pc.DragTo(coords.NewPoint(snapDestinationX, displayInfo.Bounds.Height/2), 2*time.Second))(ctx); err != nil {
return errors.Wrap(err, "failed to drag to snap from overview")
}
if err := checkCompatSnappedWindowState(ctx, tconn, a, cr, d, act, primary, stableWidth); err != nil {
return errors.Wrap(err, "failed to wait until window state change")
}
return nil
}
func testSnapViaKeyboardShortcut(ctx context.Context, tconn *chrome.TestConn, a *arc.ARC, cr *chrome.Chrome, d *ui.Device, act *arc.Activity, primary bool, stableWidth int) error {
ew, err := input.Keyboard(ctx)
if err != nil {
return errors.Wrap(err, "failed to open keyboard device")
}
defer ew.Close()
shortcutCommand := "Alt+]"
if primary {
shortcutCommand = "Alt+["
}
if err := ew.Accel(ctx, shortcutCommand); err != nil {
return errors.Wrap(err, "failed to write keyboard events")
}
if err := checkCompatSnappedWindowState(ctx, tconn, a, cr, d, act, primary, stableWidth); err != nil {
return errors.Wrap(err, "failed to wait until window state change")
}
return nil
}
func CompatSnap(ctx context.Context, s *testing.State) {
// Reserve ten seconds for various cleanup.
cleanupCtx := ctx
ctx, cancel := ctxutil.Shorten(ctx, time.Second*10)
defer cancel()
if err := power.TurnOnDisplay(ctx); err != nil {
s.Fatal("Failed to turn on display: ", err)
}
cr := s.FixtValue().(*arc.PreData).Chrome
a := s.FixtValue().(*arc.PreData).ARC
d := s.FixtValue().(*arc.PreData).UIDevice
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
s.Fatal("Creating test API connection Failed: ", err)
}
pc := pointer.NewMouse(tconn)
defer pc.Close()
displayInfo, err := display.GetPrimaryInfo(ctx, tconn)
if err != nil {
s.Fatal("Failed to get the primary display info: ", err)
}
// Ensures landscape orientation.
orientation, err := display.GetOrientation(ctx, tconn)
if err != nil {
s.Fatal("Failed to obtain the orientation info: ", err)
}
if orientation.Type == display.OrientationPortraitPrimary {
if err = display.SetDisplayRotationSync(ctx, tconn, displayInfo.ID, display.Rotate90); err != nil {
s.Fatal("Failed to rotate display: ", err)
}
defer display.SetDisplayRotationSync(cleanupCtx, tconn, displayInfo.ID, display.Rotate0)
}
// Set a pure white wallpaper to reduce the noises on a screenshot because currently wm.CheckResizeLockState checks the visibility of the translucent window border based on a screenshot.
// The wallpaper will exist continuous if the Chrome session gets reused.
ui := uiauto.New(tconn)
if err := ashwallpaper.OpenWallpaperPicker(ui)(ctx); err != nil {
s.Fatal("Failed to open wallpaper picker: ", err)
}
if err := ashwallpaper.SelectCollection(ui, ashwallpaperconstants.SolidColorsCollection)(ctx); err != nil {
s.Fatal("Failed to select wallpaper collection: ", err)
}
if err := ashwallpaper.SelectImage(ui, "White")(ctx); err != nil {
s.Fatal("Failed to select wallpaper image: ", err)
}
if err := ashwallpaper.CloseWallpaperPicker()(ctx); err != nil {
s.Fatal("Failed to close wallpaper picker: ", err)
}
// Install the test app.
if err := a.Install(ctx, arc.APKPath(wm.ResizeLockApkName), adb.InstallOptionFromPlayStore); err != nil {
s.Fatal("Failed to install app from PlayStore: ", err)
}
defer a.Uninstall(cleanupCtx, wm.ResizeLockTestPkgName)
// Launch the test app.
act, err := arc.NewActivity(a, wm.ResizeLockTestPkgName, wm.ResizeLockMainActivityName)
if err != nil {
s.Fatal("Failed to create a new activity: ", err)
}
defer act.Close()
if err := act.Start(ctx, tconn); err != nil {
s.Fatal("Failed to start the activity: ", err)
}
defer act.Stop(cleanupCtx, tconn)
if err := ash.WaitForVisible(ctx, tconn, act.PackageName()); err != nil {
s.Fatal("Failed to wait until the activity gets visible: ", err)
}
if err := d.WaitForIdle(ctx, 10*time.Second); err != nil {
s.Fatal("Failed to wait for Android to be idle: ", err)
}
// Close the compat mode splash dialog.
if err := wm.CheckVisibility(ctx, tconn, wm.BubbleDialogClassName, true); err != nil {
s.Fatal("Failed to wait for splash: ", err)
}
if err := wm.CloseSplash(ctx, tconn, wm.InputMethodClick, nil); err != nil {
s.Fatal("Failed to close splash: ", err)
}
// Measure the window width of the stable state.
window, err := ash.GetARCAppWindowInfo(ctx, tconn, act.PackageName())
if err != nil {
s.Fatal("Failed to get window info: ", err)
}
stableWidth := window.BoundsInRoot.Width
defer ash.SetOverviewModeAndWait(cleanupCtx, tconn, false)
for _, primary := range []bool{false, true} {
// Case A. Snap the resize-locked window from overview mode.
if err := testSnapFromOverview(ctx, tconn, a, cr, pc, displayInfo, d, act, primary, stableWidth); err != nil {
s.Fatalf("Failed to snap window from overview (primary=%t): %v", primary, err)
}
if err := testUnsnapByDragging(ctx, tconn, a, cr, pc, displayInfo, d, act, stableWidth); err != nil {
s.Fatalf("Failed to unsnap window (primary=%t): %v", primary, err)
}
// Case B. Snap the resize-locked window by dragging the caption bar to the edge of the screen.
if err := testSnapByDragToSnap(ctx, tconn, a, cr, pc, displayInfo, d, act, primary, stableWidth); err != nil {
s.Fatalf("Failed to snap window by drag-to-snap (primary=%t): %v", primary, err)
}
if err := testUnsnapByDragging(ctx, tconn, a, cr, pc, displayInfo, d, act, stableWidth); err != nil {
s.Fatalf("Failed to unsnap window (primary=%t): %v", primary, err)
}
// Case C. Snap the resize-locked window via keyboard shortcut.
if err := testSnapViaKeyboardShortcut(ctx, tconn, a, cr, d, act, primary, stableWidth); err != nil {
s.Fatalf("Failed to snap window via keyboard shortcut (primary=%t): %v", primary, err)
}
if err := testUnsnapByDragging(ctx, tconn, a, cr, pc, displayInfo, d, act, stableWidth); err != nil {
s.Fatalf("Failed to unsnap window (primary=%t): %v", primary, err)
}
}
}