blob: 1ec393ac7e0f2f5e5da74cdc3a722f6f74c00cd0 [file] [log] [blame]
// Copyright 2019 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 camera
import (
"context"
"fmt"
"image/jpeg"
"os"
"path/filepath"
"time"
"github.com/pixelbender/go-matroska/matroska"
"chromiumos/tast/errors"
"chromiumos/tast/local/bundles/cros/camera/cca"
"chromiumos/tast/local/chrome"
"chromiumos/tast/local/media/caps"
"chromiumos/tast/testing"
)
// resolutionType is different capture resolution type.
type resolutionType string
const (
photoResolution resolutionType = "photo"
videoResolution = "video"
)
func init() {
testing.AddTest(&testing.Test{
Func: CCAUIResolutions,
Desc: "Opens CCA and verifies video recording related use cases",
Contacts: []string{"inker@chromium.org", "chromeos-camera-eng@google.com"},
Attr: []string{"informational"},
SoftwareDeps: []string{"chrome", "arc_camera3", caps.BuiltinOrVividCamera},
Data: []string{"cca_ui.js"},
Pre: chrome.LoggedIn(),
})
}
func CCAUIResolutions(ctx context.Context, s *testing.State) {
cr := s.PreValue().(*chrome.Chrome)
app, err := cca.New(ctx, cr, []string{s.DataPath("cca_ui.js")})
if err != nil {
s.Fatal("Failed to open CCA: ", err)
}
defer app.Close(ctx)
restartApp := func() {
if err := app.Restart(ctx); err != nil {
s.Fatal("Failed to restart CCA: ", err)
}
if err := app.WaitForVideoActive(ctx); err != nil {
s.Fatal("Preview is inactive after restart App: ", err)
}
}
if err := app.WaitForVideoActive(ctx); err != nil {
s.Fatal("Preview is inactive after launching App: ", err)
}
if noMenu, err := app.GetState(ctx, "no-resolution-settings"); err != nil {
s.Fatal(`Failed to get "no-resolution-settings" state: `, err)
} else if noMenu {
s.Fatal("Resolution settings menu is not available on device")
}
saveDir, err := cca.GetSavedDir(ctx, cr)
if err != nil {
s.Fatal("Failed to get save dir: ", err)
}
if err := testPhotoResolution(ctx, app, saveDir); err != nil {
s.Error("Failed in testPhotoResolution(): ", err)
restartApp()
}
if err := testVideoResolution(ctx, app, saveDir); err != nil {
s.Error("Failed in testVideoResolution(): ", err)
restartApp()
}
}
// getOrientedResolution gets resolution with respect to screen orientation.
func getOrientedResolution(ctx context.Context, app *cca.App, r cca.Resolution) (cca.Resolution, error) {
orientation, err := app.GetScreenOrientation(ctx)
if err != nil {
return r, err
}
isLandscape := (orientation == cca.LandscapePrimary || orientation == cca.LandscapeSecondary)
if isLandscape != (r.Width > r.Height) {
r.Width, r.Height = r.Height, r.Width
}
testing.ContextLogf(ctx, "Screen orientation %v, resolution after orientation %dx%d", orientation, r.Width, r.Height)
return r, nil
}
func testPhotoResolution(ctx context.Context, app *cca.App, saveDir string) error {
return app.RunThroughCameras(ctx, func(facing cca.Facing) error {
if err := app.SwitchMode(ctx, cca.Photo); err != nil {
return errors.Wrap(err, "failed to switch to photo mode")
}
rs, err := app.GetPhotoResolutions(ctx)
if err != nil {
return errors.Wrap(err, "failed to get photo resolution")
}
for i, r := range rs {
// CCA UI will filter out photo resolutions of megapixels < 0.1 i.e. megapixels 0.0
if r.Width*r.Height < 100000 {
continue
}
testing.ContextLogf(ctx, "Switch to photo %dx%d resolution", r.Width, r.Height)
if err := switchResolution(ctx, app, photoResolution, facing, i); err != nil {
return errors.Wrapf(err, "failed to switch to photo resolution %dx%d", r.Width, r.Height)
}
or, err := getOrientedResolution(ctx, app, r)
if err != nil {
return err
}
pr, err := app.GetPreviewResolution(ctx)
if err != nil {
return err
}
if pr.Width*or.Height != pr.Height*or.Width {
return errors.Wrapf(err, "inconsistent preview aspect ratio get %d:%d; want %d:%d", pr.Width, pr.Height, or.Width, or.Height)
}
info, err := app.TakeSinglePhoto(ctx, cca.TimerOff)
if err != nil {
return errors.Wrap(err, "failed to take photo")
}
f, err := os.Open(filepath.Join(saveDir, info[0].Name()))
if err != nil {
return errors.Wrap(err, "failed to open captured file")
}
c, err := jpeg.DecodeConfig(f)
if err != nil {
return errors.Wrap(err, "failed to decode captured file")
}
if c.Width != or.Width || c.Height != or.Height {
return errors.Wrapf(err, "incorrect captured resolution get %dx%d; want %dx%d", c.Width, c.Height, or.Width, or.Height)
}
}
return nil
})
}
// getVideoTrack get video track from video file under specified path.
func getVideoTrack(path string) (*matroska.VideoTrack, error) {
doc, err := matroska.Decode(path)
if err != nil {
return nil, errors.Wrapf(err, "failed to decode video file of path %v", path)
}
for _, track := range doc.Segment.Tracks {
for _, ent := range track.Entries {
if ent.Type == matroska.TrackTypeVideo {
return ent.Video, nil
}
}
}
return nil, errors.Errorf("no video track found in the file %v", path)
}
func testVideoResolution(ctx context.Context, app *cca.App, saveDir string) error {
return app.RunThroughCameras(ctx, func(facing cca.Facing) error {
if err := app.SwitchMode(ctx, cca.Video); err != nil {
return errors.Wrap(err, "failed to switch to video mode")
}
rs, err := app.GetVideoResolutions(ctx)
if err != nil {
return errors.Wrap(err, "failed to get video resolution")
}
for i, r := range rs {
testing.ContextLogf(ctx, "Switch to %dx%d video resolution", r.Width, r.Height)
if err := switchResolution(ctx, app, videoResolution, facing, i); err != nil {
return errors.Wrapf(err, "failed to switch to video resolution %dx%d", r.Width, r.Height)
}
or, err := getOrientedResolution(ctx, app, r)
if err != nil {
return err
}
pr, err := app.GetPreviewResolution(ctx)
if err != nil {
return err
}
if pr.Width*or.Height != pr.Height*or.Width {
return errors.Wrapf(err, "inconsistent preview aspect ratio get %d:%d; want %d:%d", pr.Width, pr.Height, or.Width, or.Height)
}
info, err := app.RecordVideo(ctx, cca.TimerOff, time.Second)
if err != nil {
return errors.Wrap(err, "failed to record video")
}
track, err := getVideoTrack(filepath.Join(saveDir, info.Name()))
if err != nil {
return err
}
if track.Width != or.Width || track.Height != or.Height {
return errors.Wrapf(err, "incorrect captured resolution get %dx%d; want %dx%d", track.Width, track.Height, r.Width, r.Height)
}
}
return nil
})
}
// switchResolution toggles the i'th resolution of specified capture resolution of specified camera facing.
func switchResolution(ctx context.Context, app *cca.App, rt resolutionType, facing cca.Facing, index int) error {
testing.ContextLogf(ctx, "Switch to %v th %v facing %v resolution", index, facing, rt)
openSetting := func(name, selector string) error {
testing.ContextLogf(ctx, "Open %q view", name)
if err := app.ClickWithSelector(ctx, selector); err != nil {
return err
}
if active, err := app.GetState(ctx, name); err != nil {
return errors.Wrap(err, "failed to get view open state")
} else if active != true {
return errors.Errorf("view %q is not openned", name)
}
return nil
}
closeSetting := func(name string) {
testing.ContextLogf(ctx, "Close %q view", name)
back := (map[string]cca.UIComponent{
"view-settings": cca.SettingsBackButton,
"view-resolution-settings": cca.ResolutionSettingBackButton,
"view-photo-resolution-settings": cca.PhotoResolutionSettingBackButton,
"view-video-resolution-settings": cca.VideoResolutionSettingBackButton,
})[name]
app.Click(ctx, back)
}
if err := openSetting("view-settings", "#open-settings"); err != nil {
return err
}
defer closeSetting("view-settings")
if err := openSetting("view-resolution-settings", "#settings-resolution"); err != nil {
return err
}
defer closeSetting("view-resolution-settings")
fname, ok := (map[cca.Facing]string{
cca.FacingBack: "back",
cca.FacingFront: "front",
cca.FacingExternal: "external",
})[facing]
if !ok {
return errors.Errorf("cannot switch resolution of unsuppport facing %v", facing)
}
view := fmt.Sprintf("view-%s-resolution-settings", rt)
settingSelector := fmt.Sprintf("#settings-%s-%sres", fname, rt)
if facing == cca.FacingExternal {
id, err := app.GetDeviceID(ctx)
if err != nil {
return errors.Wrap(err, "failed to get device id of external camera")
}
settingSelector = fmt.Sprintf("button[aria-describedby='%s-%sres-desc']", id, rt)
}
if err := openSetting(view, settingSelector); err != nil {
return err
}
defer closeSetting(view)
optionUI := cca.PhotoResolutionOption
if rt == videoResolution {
optionUI = cca.VideoResolutionOption
}
if err := app.ClickWithIndex(ctx, optionUI, index); err != nil {
return errors.Wrap(err, "failed to click on resolution item")
}
if err := app.WaitForVideoActive(ctx); err != nil {
return errors.Wrap(err, "preview is inactive after switching resolution")
}
return nil
}