blob: 77372b7bebde4fd7d5cc78b43e3de0e85064b5af [file] [log] [blame]
// Copyright 2020 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"
"image"
"image/color"
"time"
"chromiumos/tast/errors"
androidui "chromiumos/tast/local/android/ui"
"chromiumos/tast/local/arc"
"chromiumos/tast/local/chrome/ui"
"chromiumos/tast/local/colorcmp"
"chromiumos/tast/local/coords"
"chromiumos/tast/local/screenshot"
"chromiumos/tast/testing"
)
func init() {
testing.AddTest(&testing.Test{
Func: ShelfIcons,
Desc: "Tests that ARC++ windows are represented in the shelf correctly, including grouping of windows and custom icons",
Contacts: []string{"phweiss@chromium.org", "giovax@chromium.org", "arc-framework+tast@google.com"},
SoftwareDeps: []string{"chrome"},
Fixture: "arcBooted",
Data: []string{"ArcShelfIconTest.apk"},
Timeout: 30 * time.Second,
Params: []testing.Param{{
ExtraAttr: []string{"group:mainline", "informational"},
ExtraSoftwareDeps: []string{"android_p"},
}, {
Name: "vm",
ExtraAttr: []string{"group:mainline", "informational"},
ExtraSoftwareDeps: []string{"android_vm"},
}},
})
}
// ShelfIcons opens 4 windows of an app that will set custom icons and window
// titles. The 4 windows will be grouped in groups of 2. The test then checks
// that the grouping is respected by the shelf, and that the icons and titles
// are displayed correctly when clicking the shelf entries.
func ShelfIcons(ctx context.Context, s *testing.State) {
const (
apk = "ArcShelfIconTest.apk"
pkg = "org.chromium.arc.testapp.arcshelficontest"
cls = "org.chromium.arc.testapp.arcshelficontest.ShelfIconActivity"
idPrefix = "org.chromium.arc.testapp.arcshelficontest:id/"
titleID = idPrefix + "windowtitle"
titleSuffix = "_title"
appTitle = "ArcShelfIconTest"
menuIconOffset = 8
colorMaxDiff = 32
)
var (
iconColors = []string{"white", "red", "green", "blue"}
windowGroups = []string{"group1", "group2", "group2", "group1"}
expGroupTitles = [][]string{{"blue", "white"}, {"green", "red"}}
expGroupColor = [][]color.RGBA{{color.RGBA{0, 0, 255, 255}, color.RGBA{255, 255, 255, 255}}, {color.RGBA{0, 255, 0, 255}, color.RGBA{255, 0, 0, 255}}}
)
p := s.FixtValue().(*arc.PreData)
cr := p.Chrome
a := p.ARC
s.Log("Creating Test API connection")
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
s.Fatal("Failed to create Test API connection: ", err)
}
s.Log("Initializing UI Automator")
d, err := a.NewUIDevice(ctx)
if err != nil {
s.Fatal("Failed initializing UI Automator: ", err)
}
defer d.Close(ctx)
s.Log("Installing app")
if err := a.Install(ctx, s.DataPath(apk)); err != nil {
s.Fatal("Failed installing app: ", err)
}
for i, color := range iconColors {
// This command creates a new app window.
if err := a.Command(ctx,
"am", "start", "-W", "-n", pkg+"/"+cls,
// These flags (NEW_TASK, MULTIPLE_TASK) ensure that a new window is created.
"-f", "0x18000000",
// Specify color, window title and window grouping via extras.
"-e", "color", color,
"-e", "title", color+titleSuffix,
"-e", "org.chromium.arc.shelf_group_id", windowGroups[i],
).Run(); err != nil {
s.Fatal("Failed starting app: ", err)
}
s.Log("Waiting for " + color + " app to show up")
if err := d.Object(androidui.ID(titleID), androidui.Text(color+titleSuffix)).WaitForExists(ctx, 30*time.Second); err != nil {
s.Fatal("Failed to wait for the app shown: ", err)
}
}
// In the end, close all windows.
defer a.Command(ctx, "pm", "clear", pkg).Run()
// Get the root view to translate coordinates in different resolutions.
root, err := ui.Root(ctx, tconn)
if err != nil {
s.Fatal("Could not find root node: ", err)
}
params := ui.FindParams{ClassName: "ash/ShelfAppButton", Name: "ArcShelfIconTest"}
buttons, err := ui.FindAll(ctx, tconn, params)
if err != nil {
s.Fatal("Failed to find shelf button: ", err)
}
defer buttons.Release(ctx)
if len(buttons) != 2 {
s.Fatalf("Unexpected number of ArcShelfIconTest buttons: got %d, expected 2", len(buttons))
}
// There should be 2 buttons in the shelf that belong to the testing app.
// First, group1 with a blue icon (with the white and blue window
// belonging to this group), then group2 with the green icon (with the
// red and green window belong to this group). The order of the buttons
// is fixed, since the white window of group1 was created first.
// We will click both of these buttons and check the context menu
// if the right windows are being listed, and then check the button color.
for i, button := range buttons {
if err := button.LeftClick(ctx); err != nil {
s.Fatalf("Failed to click button %d: %s", i+1, err)
}
// Wait for menu animation to finish. It should take less than 150ms.
// If we didn't wait long enough, we will retry screenshotting
// when we start comparing colors.
if err := testing.Sleep(ctx, 150*time.Millisecond); err != nil {
s.Fatal("Waiting for finished animation failed: ", err)
}
img, err := screenshot.GrabScreenshot(ctx, cr)
if err != nil {
s.Fatal("Failed to grab screenshot: ", err)
}
menuItemsParams := ui.FindParams{ClassName: "MenuItemView"}
menuItems, err := ui.FindAll(ctx, tconn, menuItemsParams)
if err != nil {
s.Fatal("Could not find menu items: ", err)
}
// The menu for one group should list 3 items: a header and one per window in the group
if len(menuItems) != len(expGroupTitles[i])+1 {
itemLog := "menuItems: "
for _, item := range menuItems {
itemLog += "\"" + item.Name + "\" "
}
s.Log(itemLog)
s.Fatalf("Found %d menu items, expected %d", len(menuItems), len(expGroupTitles[i])+1)
}
// Check if animation of menu item is finished. The menu is simply fading in, samplePt stays constant.
samplePt := getIconCoordsFromUINode(menuItems[1], menuIconOffset, img, root)
expColor := expGroupColor[i][0]
sampleColor := img.At(samplePt.X, samplePt.Y)
if !colorcmp.ColorsMatch(sampleColor, expColor, colorMaxDiff) {
s.Logf("Color %v is not close enough to %v, repeating screenshot in case animation is not done", sampleColor, expColor)
if err := testing.Poll(ctx, func(ctx context.Context) error {
img, err = screenshot.GrabScreenshot(ctx, cr)
if err != nil {
s.Fatal("Failed to grab screenshot: ", err)
}
sampleColor = img.At(samplePt.X, samplePt.Y)
if !colorcmp.ColorsMatch(sampleColor, expColor, colorMaxDiff) {
return errors.Errorf("Waiting for color %v to be close to %v", sampleColor, expColor)
}
return nil
}, &testing.PollOptions{Timeout: 5 * time.Second}); err != nil {
s.Fatal("Failed to wait for context menu animation to finish: ", err)
}
}
// Animation is done, compare all menu titles and icons.
for j, menuItem := range menuItems {
// The first item is the header without icon.
if j == 0 {
if menuItem.Name != appTitle {
s.Errorf("Got menu title %s, expected %s", menuItem.Name, appTitle)
}
continue
}
expColor := expGroupColor[i][j-1]
expTitle := expGroupTitles[i][j-1] + titleSuffix
if menuItem.Name != expTitle {
s.Errorf("Got menu title %d, %d as %s, expected %s", i, j, menuItem.Name, expTitle)
}
samplePt := getIconCoordsFromUINode(menuItem, menuIconOffset, img, root)
iconColor := img.At(samplePt.X, samplePt.Y)
if !colorcmp.ColorsMatch(iconColor, expColor, colorMaxDiff) {
s.Errorf("Got icon color %v, not close enough to %v", iconColor, expColor)
}
}
// Check icon on the shelf button.
buttonCoords := getIconCoordsFromUINode(button, 0 /*iconOffset*/, img, root)
buttonColor := img.At(buttonCoords.X, buttonCoords.Y)
if !colorcmp.ColorsMatch(buttonColor, expGroupColor[i][0], colorMaxDiff) {
s.Errorf("Button color %v was not close enough to %v", buttonColor, expGroupColor[i])
}
if err := button.LeftClick(ctx); err != nil {
s.Fatal("Could not close context menu: ", err)
}
}
}
// adjustResolution is a helper function to convert a point from the resolution
// of the ui.Node coordinate system to the coordinate system of a screenshot.
func adjustResolution(p coords.Point, img image.Image, root *ui.Node) coords.Point {
return coords.NewPoint(p.X*img.Bounds().Dx()/root.Location.Width, p.Y*img.Bounds().Dy()/root.Location.Height)
}
// getIconCoordsFromUINode takes a UI Node, and outputs the coordinates of
// a pixel that should be close to the center of the item's icon.
func getIconCoordsFromUINode(item *ui.Node, iconOffset int, img image.Image, root *ui.Node) coords.Point {
// This is the layout of one menu item, we want the color of O:
// /-------------------\
// | /-\ |
// | |O| green_title |
// | \-/ |
// \-------------------/
//
// |--| iconOffset
//
// This is the layout of a shelf button, iconOffset=0:
// /---\
// |/-\|
// ||O||
// |\-/|
// \---/
topLeftPt := item.Location.TopLeft()
offsetPt := coords.NewPoint(iconOffset+item.Location.Height/2, item.Location.Height/2)
samplePt := adjustResolution(topLeftPt.Add(offsetPt), img, root)
return samplePt
}