blob: eab760448092ec2d93a7cba888fcd7522d65cb6f [file] [log] [blame]
// Copyright 2021 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 nearbyshare
import (
"context"
"encoding/json"
"os"
"path/filepath"
"time"
"github.com/golang/protobuf/ptypes/empty"
"google.golang.org/grpc"
nearbycommon "chromiumos/tast/common/cros/nearbyshare"
"chromiumos/tast/common/cros/nearbyshare/nearbysetup"
"chromiumos/tast/common/cros/nearbyshare/nearbytestutils"
"chromiumos/tast/common/testexec"
"chromiumos/tast/errors"
"chromiumos/tast/fsutil"
"chromiumos/tast/local/bluetooth"
"chromiumos/tast/local/chrome"
"chromiumos/tast/local/chrome/nearbyshare"
"chromiumos/tast/local/chrome/uiauto/filesapp"
"chromiumos/tast/local/syslog"
"chromiumos/tast/services/cros/nearbyservice"
"chromiumos/tast/testing"
)
func init() {
testing.AddService(&testing.Service{
Register: func(srv *grpc.Server, s *testing.ServiceState) {
nearbyservice.RegisterNearbyShareServiceServer(srv, &NearbyService{s: s})
},
})
}
// NearbyService implements tast.cros.nearbyservice.NearbyShareService.
type NearbyService struct {
s *testing.ServiceState
cr *chrome.Chrome
tconn *chrome.TestConn
deviceName string
senderSurface *nearbyshare.SendSurface
receiverSurface *nearbyshare.ReceiveSurface
chromeReader *syslog.LineReader
messageReader *syslog.LineReader
fileNames []string
username string
dataUsage nearbysetup.DataUsage
visibility nearbysetup.Visibility
btsnoopCmd *testexec.Cmd
}
// NewChromeLogin logs into Chrome with Nearby Share flags enabled.
func (n *NearbyService) NewChromeLogin(ctx context.Context, req *nearbyservice.CrOSLoginRequest) (*empty.Empty, error) {
if n.cr != nil {
return nil, errors.New("Chrome already available")
}
nearbyOpts := []chrome.Option{
chrome.DisableFeatures("SplitSettingsSync"),
chrome.ExtraArgs("--nearby-share-verbose-logging", "--enable-logging", "--vmodule=*blue*=1", "--vmodule=*nearby*=1"),
}
n.username = chrome.DefaultUser
if req.Username != "" {
n.username = req.Username
nearbyOpts = append(nearbyOpts, chrome.GAIALogin(chrome.Creds{User: req.Username, Pass: req.Password}))
}
if req.KeepState {
nearbyOpts = append(nearbyOpts, chrome.KeepState())
}
cr, err := chrome.New(ctx, nearbyOpts...)
if err != nil {
testing.ContextLog(ctx, "Failed to start Chrome")
return nil, err
}
n.cr = cr
tconn, err := cr.TestAPIConn(ctx)
if err != nil {
testing.ContextLog(ctx, "Failed to get a connection to the Test Extension")
return nil, err
}
n.tconn = tconn
return &empty.Empty{}, nil
}
// CloseChrome closes all surfaces and Chrome.
// This will likely be called in a defer in remote tests instead of called explicitly. So log everything that fails to aid debugging later.
func (n *NearbyService) CloseChrome(ctx context.Context, req *empty.Empty) (*empty.Empty, error) {
if n.cr == nil {
testing.ContextLog(ctx, "Chrome not available")
return nil, errors.New("Chrome not available")
}
os.RemoveAll(nearbytestutils.SendDir)
if n.senderSurface != nil {
if err := n.senderSurface.Close(ctx); err != nil {
testing.ContextLog(ctx, "Closing SendSurface failed: ", err)
}
}
if n.receiverSurface != nil {
if err := n.receiverSurface.Close(ctx); err != nil {
testing.ContextLog(ctx, "Closing ReceiveSurface failed: ", err)
}
}
err := n.cr.Close(ctx)
if err != nil {
testing.ContextLog(ctx, "Faied to close Chrome in Nearby Share service: ", err)
} else {
testing.ContextLog(ctx, "Nearby Share service closed successfully for: ", n.deviceName)
}
n.cr = nil
return &empty.Empty{}, err
}
// StartLogging starts logging at the start of a test.
func (n *NearbyService) StartLogging(ctx context.Context, req *empty.Empty) (*empty.Empty, error) {
chromeReader, err := nearbytestutils.StartLogging(ctx, syslog.ChromeLogFile)
if err != nil {
return &empty.Empty{}, errors.Wrap(err, "failed to start Chrome logging")
}
messageReader, err := nearbytestutils.StartLogging(ctx, syslog.MessageFile)
if err != nil {
return &empty.Empty{}, errors.Wrap(err, "failed io start Message logging")
}
testing.ContextLog(ctx, "Started logging chrome and message logs")
n.chromeReader = chromeReader
n.messageReader = messageReader
n.btsnoopCmd = bluetooth.StartBTSnoopLogging(n.s.ServiceContext(), filepath.Join(os.TempDir(), nearbycommon.BtsnoopLog))
if err := n.btsnoopCmd.Start(); err != nil {
return &empty.Empty{}, errors.Wrap(err, "failed to start btmon")
}
return &empty.Empty{}, err
}
// SaveLogs saves the chrome and messages logs on the DUT.
func (n *NearbyService) SaveLogs(ctx context.Context, req *empty.Empty) (*empty.Empty, error) {
var err error
if err = os.RemoveAll(nearbycommon.NearbyLogDir); err != nil {
testing.ContextLog(ctx, "Failed to delete nearby log dir: ", err)
}
if err = os.Mkdir(nearbycommon.NearbyLogDir, 0755); err != nil {
testing.ContextLog(ctx, "Failed to create tmp dir log: ", err)
}
if err = nearbytestutils.SaveLogs(ctx, n.chromeReader, filepath.Join(nearbycommon.NearbyLogDir, nearbycommon.ChromeLog)); err != nil {
testing.ContextLog(ctx, "Failed to save chrome log: ", err)
}
if err = nearbytestutils.SaveLogs(ctx, n.messageReader, filepath.Join(nearbycommon.NearbyLogDir, nearbycommon.MessageLog)); err != nil {
testing.ContextLog(ctx, "Failed to save message log: ", err)
}
if err := n.btsnoopCmd.Kill(); err != nil {
testing.ContextLog(ctx, "Failed to kill btmon: ", err)
}
n.btsnoopCmd.Wait()
if err := fsutil.CopyFile(filepath.Join(os.TempDir(), nearbycommon.BtsnoopLog), filepath.Join(nearbycommon.NearbyLogDir, nearbycommon.BtsnoopLog)); err != nil {
testing.ContextLog(ctx, "Failed to save btsnoop log: ", err)
}
return &empty.Empty{}, err
}
// CrOSSetup performs Nearby Share setup on a ChromeOS device.
func (n *NearbyService) CrOSSetup(ctx context.Context, req *nearbyservice.CrOSSetupRequest) (*empty.Empty, error) {
if n.cr == nil {
return nil, errors.New("Chrome not available")
}
n.deviceName = req.DeviceName
n.dataUsage = nearbysetup.DataUsage(req.DataUsage)
n.visibility = nearbysetup.Visibility(req.Visibility)
if err := nearbysetup.CrOSSetup(ctx, n.tconn, n.cr, n.dataUsage, n.visibility, n.deviceName); err != nil {
return nil, errors.Wrap(err, "failed to perform CrOS setup")
}
if n.visibility == nearbysetup.VisibilitySelectedContacts && req.SenderUsername != "" {
nearbySettings, err := nearbyshare.LaunchNearbySettings(ctx, n.tconn, n.cr)
if err != nil {
return nil, errors.Wrap(err, "failed to launch OS settings")
}
defer nearbySettings.Close(ctx)
if err := nearbySettings.SetAllowedContacts(ctx, req.SenderUsername); err != nil {
return nil, errors.Wrap(err, "failed to set allowed contacts")
}
}
return &empty.Empty{}, nil
}
// StartHighVisibilityMode starts high vis mode using the UI library.
func (n *NearbyService) StartHighVisibilityMode(ctx context.Context, req *empty.Empty) (*empty.Empty, error) {
if n.cr == nil {
return nil, errors.New("Chrome not available")
}
return &empty.Empty{}, nearbyshare.StartHighVisibilityMode(ctx, n.tconn, n.deviceName)
}
// PrepareFiles extracts test files.
func (n *NearbyService) PrepareFiles(ctx context.Context, req *nearbyservice.CrOSPrepareFileRequest) (*nearbyservice.CrOSPrepareFileResponse, error) {
if n.cr == nil {
return nil, errors.New("Chrome not available")
}
filenames, err := nearbytestutils.ExtractCrosTestFiles(ctx, req.FileName)
if err != nil {
testing.ContextLog(ctx, "Failed to extract test files")
return nil, err
}
res := nearbyservice.CrOSPrepareFileResponse{FileNames: filenames}
return &res, nil
}
// StartSend starts to share files.
func (n *NearbyService) StartSend(ctx context.Context, req *nearbyservice.CrOSSendFilesRequest) (*empty.Empty, error) {
if n.cr == nil {
return nil, errors.New("Chrome not available")
}
if n.senderSurface != nil {
n.senderSurface.Close(ctx)
}
// Get the full paths of the test files to pass to chrome://nearby.
var testFiles []string
for _, f := range req.FileNames {
testFiles = append(testFiles, filepath.Join(nearbytestutils.SendDir, f))
}
sender, err := nearbyshare.StartSendFiles(ctx, n.cr, testFiles)
if err != nil {
return nil, errors.Wrap(err, "failed to set up control over the send surface")
}
n.senderSurface = sender
return &empty.Empty{}, nil
}
// SelectShareTarget selects the expected receiver in the sending window.
func (n *NearbyService) SelectShareTarget(ctx context.Context, req *nearbyservice.CrOSSelectShareTargetRequest) (*nearbyservice.CrOSShareTokenResponse, error) {
if n.cr == nil {
return nil, errors.New("Chrome not available")
}
if n.senderSurface == nil {
return nil, errors.New("SendSurface is not defined")
}
if err := n.senderSurface.SelectShareTarget(ctx, req.ReceiverName, nearbycommon.DetectShareTargetTimeout); err != nil {
return nil, errors.Wrap(err, "failed to select share target")
}
var res nearbyservice.CrOSShareTokenResponse
if req.CollectShareToken {
token, err := n.senderSurface.ConfirmationToken(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to get confirmation token")
}
res.ShareToken = token
}
return &res, nil
}
// StartReceiving enables high vis mode receiving via Javascript.
func (n *NearbyService) StartReceiving(ctx context.Context, req *empty.Empty) (*empty.Empty, error) {
if n.cr == nil {
return nil, errors.New("Chrome not available")
}
receiver, err := nearbyshare.StartReceiving(ctx, n.tconn, n.cr)
if err != nil {
return nil, errors.Wrap(err, "failed to set up control over the receiving surface")
}
n.receiverSurface = receiver
return &empty.Empty{}, nil
}
// WaitForSenderAndAcceptShare is called by a receiver to wait for a sender to appear in their list and accepts the share from them.
func (n *NearbyService) WaitForSenderAndAcceptShare(ctx context.Context, req *nearbyservice.CrOSReceiveFilesRequest) (*nearbyservice.CrOSShareTokenResponse, error) {
if n.cr == nil {
return nil, errors.New("Chrome not available")
}
if n.receiverSurface == nil {
return nil, errors.New("ReceiveSurface is not defined")
}
var res nearbyservice.CrOSShareTokenResponse
token, err := n.receiverSurface.WaitForSender(ctx, req.SenderName, nearbycommon.DetectShareTargetTimeout)
if err != nil {
return nil, errors.Wrap(err, "CrOS receiver failed to find CrOS sender")
}
res.ShareToken = token
if err := n.receiverSurface.AcceptShare(ctx); err != nil {
return nil, errors.Wrap(err, "CrOS receiver failed to accept share from CrOS sender")
}
if err := nearbyshare.WaitForReceivingCompleteNotification(ctx, n.tconn, req.SenderName, time.Duration(req.TransferTimeoutSeconds)*time.Second); err != nil {
return nil, errors.Wrap(err, "failed waiting for notification to indicate sharing has completed on CrOS")
}
return &res, nil
}
// FilesHashes takes some filenames and returns a list of their hashes.
func (n *NearbyService) FilesHashes(ctx context.Context, req *nearbyservice.CrOSFileHashRequest) (*nearbyservice.CrOSFileHashResponse, error) {
if n.cr == nil {
return nil, errors.New("Chrome not available")
}
var res nearbyservice.CrOSFileHashResponse
hashes, err := nearbytestutils.HashFiles(ctx, req.FileNames, req.FileDir)
if err != nil {
return nil, errors.Wrapf(err, "could not get hash of %s ", req.FileNames)
}
n.fileNames = req.FileNames
res.Hashes = hashes
return &res, nil
}
// AcceptIncomingShareNotificationAndWaitForCompletion accepts the incoming transfer via notification. Used for in contact tests.
func (n *NearbyService) AcceptIncomingShareNotificationAndWaitForCompletion(ctx context.Context, req *nearbyservice.CrOSReceiveFilesRequest) (*empty.Empty, error) {
if n.cr == nil {
return nil, errors.New("Chrome not available")
}
if err := nearbyshare.AcceptIncomingShareNotification(ctx, n.tconn, req.SenderName, nearbycommon.DetectShareTargetTimeout); err != nil {
return nil, errors.Wrap(err, "CrOS receiver failed to accept Nearby Share notification")
}
testing.ContextLog(ctx, "Accepted the share on the CrOS receiver")
testing.ContextLog(ctx, "Waiting for receiving-complete notification on CrOS receiver")
if err := nearbyshare.WaitForReceivingCompleteNotification(ctx, n.tconn, req.SenderName, time.Duration(req.TransferTimeoutSeconds)*time.Second); err != nil {
return nil, errors.Wrap(err, "failed waiting for notification to indicate sharing has completed on CrOS")
}
return &empty.Empty{}, nil
}
// ClearTransferredFiles clears the transferred files in the receivers Downloads folder.
func (n *NearbyService) ClearTransferredFiles(ctx context.Context, req *empty.Empty) (*empty.Empty, error) {
for _, f := range n.fileNames {
filePath := filepath.Join(filesapp.DownloadPath, f)
testing.ContextLog(ctx, "file to delete: ", filePath)
if err := os.Remove(filePath); err != nil {
return nil, errors.Wrapf(err, "failed to remove %s from Downloads on receiver", filePath)
}
}
return &empty.Empty{}, nil
}
// CrOSAttributes retrieves useful information about the DUT to aid debugging.
func (n *NearbyService) CrOSAttributes(ctx context.Context, req *empty.Empty) (*nearbyservice.CrOSAttributesResponse, error) {
crosAttributes, err := nearbysetup.GetCrosAttributes(ctx, n.tconn, n.deviceName, n.username, n.dataUsage, n.visibility)
if err != nil {
return nil, errors.Wrap(err, "failed to get CrOS attributes for reporting")
}
var res nearbyservice.CrOSAttributesResponse
jsonData, err := json.Marshal(crosAttributes)
if err != nil {
return nil, errors.Wrap(err, "failed to marshal CrOS attributes")
}
res.Attributes = string(jsonData)
return &res, nil
}