camera: Add ptzControl type encapsulate ptz actions

BUG=b:187889721
TEST=tast run <DUT> camera.CCAUIPTZ

Change-Id: I59199385a590a36145745399daea7587492650d7
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/tast-tests/+/2947267
Tested-by: Inker Kuo <inker@chromium.org>
Auto-Submit: Inker Kuo <inker@chromium.org>
Reviewed-by: Wei Lee <wtlee@chromium.org>
Reviewed-by: Gabor Zsolt Magda <gabormagda@google.com>
Commit-Queue: Inker Kuo <inker@chromium.org>
(cherry picked from commit 42161a38b29c8734436de3457db6b5309af33ab1)
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/tast-tests/+/2982654
Commit-Queue: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
diff --git a/src/chromiumos/tast/local/bundles/cros/camera/cca_ui_ptz.go b/src/chromiumos/tast/local/bundles/cros/camera/cca_ui_ptz.go
index 6055840..ff7837f 100644
--- a/src/chromiumos/tast/local/bundles/cros/camera/cca_ui_ptz.go
+++ b/src/chromiumos/tast/local/bundles/cros/camera/cca_ui_ptz.go
@@ -133,6 +133,103 @@
 	return &image.Rectangle{*minPt, *maxPt}, nil
 }
 
+// calShift checks the size and calculates the x-y shift between |r| and |r2|.
+func calShift(r, r2 *image.Rectangle) (*image.Point, error) {
+	abs := func(n int) int {
+		if n < 0 {
+			return -n
+		}
+		return n
+	}
+
+	// The pattern should only shift without resizing.
+	sz := r.Size()
+	sz2 := r2.Size()
+
+	// Do all comparisons with 1px precision tolerance introduced by fake
+	// file VCD bilinear resizing implementation.
+	const precision = 1
+	if abs(sz.X-sz2.X) > precision {
+		return nil, errors.Errorf("inconsistent width, got %v; want %v", sz2.X, sz.X)
+	}
+	if abs(sz.Y-sz2.Y) > precision {
+		return nil, errors.Errorf("inconsistent height, got %v; want %v", sz2.Y, sz.Y)
+	}
+	return &image.Point{r2.Min.X - r.Min.X, r2.Min.Y - r.Min.Y}, nil
+}
+
+type ptzControl struct {
+	// ui is the UI toggled for moving preview in one of PTZ direction.
+	ui *cca.UIComponent
+	// testFunc tests pattern before and after ptz control applied moving in the target direction.
+	testFunc func(r, r2 *image.Rectangle) (bool, error)
+}
+
+var (
+	panLeft = ptzControl{&cca.PanLeftButton, func(r, r2 *image.Rectangle) (bool, error) {
+		shift, err := calShift(r, r2)
+		if err != nil {
+			return false, err
+		}
+		return shift.X < 0 && shift.Y == 0, nil
+	}}
+	panRight = ptzControl{&cca.PanRightButton, func(r, r2 *image.Rectangle) (bool, error) {
+		shift, err := calShift(r, r2)
+		if err != nil {
+			return false, err
+		}
+		return shift.X > 0 && shift.Y == 0, nil
+	}}
+	tiltDown = ptzControl{&cca.TiltDownButton, func(r, r2 *image.Rectangle) (bool, error) {
+		shift, err := calShift(r, r2)
+		if err != nil {
+			return false, err
+		}
+		return shift.X == 0 && shift.Y < 0, nil
+	}}
+	tiltUp = ptzControl{&cca.TiltUpButton, func(r, r2 *image.Rectangle) (bool, error) {
+		shift, err := calShift(r, r2)
+		if err != nil {
+			return false, err
+		}
+		return shift.X == 0 && shift.Y > 0, nil
+	}}
+	zoomIn = ptzControl{&cca.ZoomInButton, func(r, r2 *image.Rectangle) (bool, error) {
+		return r.Size().X < r2.Size().X && r.Size().Y < r2.Size().Y, nil
+	}}
+	zoomOut = ptzControl{&cca.ZoomOutButton, func(r, r2 *image.Rectangle) (bool, error) {
+		return r.Size().X > r2.Size().X && r.Size().Y > r2.Size().Y, nil
+	}}
+)
+
+// testToggle tests toggling the control |ctrl|.
+func (ctrl *ptzControl) testToggle(ctx context.Context, app *cca.App) error {
+	pRect, err := findPattern(ctx, app)
+	if err != nil {
+		return errors.Wrapf(err, "failed to find pattern before clicking %v: %v", ctrl.ui.Name, err)
+	}
+	if err := app.ClickPTZButton(ctx, *ctrl.ui); err != nil {
+		return errors.Wrapf(err, "failed to click: %v", err)
+	}
+	if err := testing.Poll(ctx, func(ctx context.Context) error {
+		rect, err := findPattern(ctx, app)
+		if err != nil {
+			return errors.Wrapf(err, "failed to find pattern after clicking %v: %v", ctrl.ui.Name, err)
+		}
+		result, err := ctrl.testFunc(pRect, rect)
+		if err != nil {
+			return testing.PollBreak(err)
+		}
+		if result {
+			return nil
+		}
+		return errors.Errorf("failed on testing UI with region before %v ; after %v", pRect, rect)
+	}, &testing.PollOptions{Interval: time.Second}); err != nil {
+		return errors.Wrapf(err, "failed to run %v test func: %v", ctrl.ui.Name, err)
+	}
+	return nil
+}
+
 func CCAUIPTZ(ctx context.Context, s *testing.State) {
 	y4m, err := preparePattern()
 	if err != nil {
@@ -171,89 +268,22 @@
 		s.Fatal("Failed to open ptz panel: ", err)
 	}
 
-	// Do all comparisons with 1px precision tolerance introduced by fake
-	// file VCD bilinear resizing implementation.
-	const precision = 1
-	abs := func(n int) int {
-		if n < 0 {
-			return -n
-		}
-		return n
-	}
-	calShift := func(r, r2 *image.Rectangle) (*image.Point, error) {
-		// The pattern should only shift without resizing.
-		sz := r.Size()
-		sz2 := r2.Size()
-		if abs(sz.X-sz2.X) > precision {
-			return nil, errors.Errorf("inconsistent width, got %v; want %v", sz2.X, sz.X)
-		}
-		if abs(sz.Y-sz2.Y) > precision {
-			return nil, errors.Errorf("inconsistent height, got %v; want %v", sz2.Y, sz.Y)
-		}
-		return &image.Point{r2.Min.X - r.Min.X, r2.Min.Y - r.Min.Y}, nil
-	}
-
-	for _, tc := range []struct {
-		ui       cca.UIComponent
-		testFunc func(r, r2 *image.Rectangle) (bool, error)
-	}{
-		{cca.ZoomInButton, func(r, r2 *image.Rectangle) (bool, error) {
-			return r.Size().X < r2.Size().X && r.Size().Y < r2.Size().Y, nil
-		}},
-		{cca.PanLeftButton, func(r, r2 *image.Rectangle) (bool, error) {
-			shift, err := calShift(r, r2)
-			if err != nil {
-				return false, err
-			}
-			return shift.X < 0 && shift.Y == 0, nil
-		}},
-		{cca.PanRightButton, func(r, r2 *image.Rectangle) (bool, error) {
-			shift, err := calShift(r, r2)
-			if err != nil {
-				return false, err
-			}
-			return shift.X > 0 && shift.Y == 0, nil
-		}},
-		{cca.TiltDownButton, func(r, r2 *image.Rectangle) (bool, error) {
-			shift, err := calShift(r, r2)
-			if err != nil {
-				return false, err
-			}
-			return shift.X == 0 && shift.Y < 0, nil
-		}},
-		{cca.TiltUpButton, func(r, r2 *image.Rectangle) (bool, error) {
-			shift, err := calShift(r, r2)
-			if err != nil {
-				return false, err
-			}
-			return shift.X == 0 && shift.Y > 0, nil
-		}},
-		{cca.ZoomOutButton, func(r, r2 *image.Rectangle) (bool, error) {
-			return r.Size().X > r2.Size().X && r.Size().Y > r2.Size().Y, nil
-		}},
+	// Test move all controls. The controls need to be tested in order such
+	// that |zoomIn| before all other controls(For all other controls will
+	// be disabled in minimal zoom level as behavior of digital zoom
+	// camera), |panLeft| before |panRight| (For the initial pan level is 0
+	// with range [0, 15]) with initial mirror state, |tiltDown| before
+	// |tiltUp| (For the initial tilt level is 0 with range[0, 8]).
+	for _, control := range []ptzControl{
+		zoomIn,
+		panLeft,
+		panRight,
+		tiltDown,
+		tiltUp,
+		zoomOut,
 	} {
-		pRect, err := findPattern(ctx, app)
-		if err != nil {
-			s.Fatalf("Failed to find pattern before clicking %v: %v", tc.ui.Name, err)
-		}
-		if err := app.ClickPTZButton(ctx, tc.ui); err != nil {
-			s.Fatal("Failed to click: ", err)
-		}
-		if err := testing.Poll(ctx, func(ctx context.Context) error {
-			rect, err := findPattern(ctx, app)
-			if err != nil {
-				s.Fatalf("Failed to find pattern after clicking %v: %v", tc.ui.Name, err)
-			}
-			result, err := tc.testFunc(pRect, rect)
-			if err != nil {
-				return testing.PollBreak(err)
-			}
-			if result {
-				return nil
-			}
-			return errors.Errorf("failed on testing UI with region before %v ; after %v", pRect, rect)
-		}, &testing.PollOptions{Interval: time.Second}); err != nil {
-			s.Fatalf("Failed to run %v test func: %v", tc.ui.Name, err)
+		if err := control.testToggle(ctx, app); err != nil {
+			s.Fatal("Failed: ", err)
 		}
 	}
 }