blob: ad1eceffc120e1afa63cc57b95760fd05747bec5 [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 adb
import (
"context"
"regexp"
"strconv"
"strings"
"chromiumos/tast/common/testexec"
"chromiumos/tast/errors"
"chromiumos/tast/shutil"
)
// ShellCommand returns a command in Android shell via adb.
func (d *Device) ShellCommand(ctx context.Context, name string, args ...string) *testexec.Cmd {
// adb shell executes the command via /bin/sh, so here it is necessary
// to escape.
cmd := "exec " + shutil.EscapeSlice(append([]string{name}, args...))
return d.Command(ctx, "shell", cmd)
}
// SendIntentCommand returns a Cmd to send an intent with "am start" command.
func (d *Device) SendIntentCommand(ctx context.Context, action, data string) *testexec.Cmd {
args := []string{"start", "-a", action}
if len(data) > 0 {
args = append(args, "-d", data)
}
return d.ShellCommand(ctx, "am", args...)
}
// GetProp returns the Android system property indicated by the specified key.
func (d *Device) GetProp(ctx context.Context, key string) (string, error) {
o, err := d.ShellCommand(ctx, "getprop", key).Output(testexec.DumpLogOnError)
if err != nil {
return "", err
}
return strings.TrimSpace(string(o)), nil
}
// BroadcastResult is the parsed result of an Android Activity Manager broadcast.
type BroadcastResult struct {
// The result value of the broadcast.
Result int
// Optional: Additional data to be passed with the result.
Data *string
// Optional: A bundle of extra data passed with the result.
// TODO(springerm): extras is a key-value map and should be parsed.
Extras *string
}
const (
// Activity.RESULT_OK
intentResultActivityResultOk = -1
)
// BroadcastIntent broadcasts an intent with "am broadcast" and returns the result.
func (d *Device) BroadcastIntent(ctx context.Context, action string, params ...string) (*BroadcastResult, error) {
args := []string{"broadcast", "-a", action}
args = append(args, params...)
output, err := d.ShellCommand(ctx, "am", args...).Output(testexec.DumpLogOnError)
if err != nil {
return nil, err
}
// TODO(springerm): Find a way to report metrics from Android apps, so we can avoid this parsing hackery.
// broadcastResultRegexp matches the result from an Android Activity Manager broadcast.
broadcastResultRegexp := regexp.MustCompile(`Broadcast completed: result=(-?[0-9]+)(, data="((\\.|[^\\"])*)")?(, extras: Bundle\[(.*)\])?`)
m := broadcastResultRegexp.FindSubmatch(output)
if m == nil {
return nil, errors.Errorf("unable to parse broadcast result for %s: %q", action, output)
}
resultValue, err := strconv.Atoi(string(m[1]))
if err != nil {
return nil, errors.Errorf("unable to parse broadcast result value for %s: %q", action, string(m[1]))
}
broadcastResult := BroadcastResult{}
broadcastResult.Result = resultValue
// `m[3]` matches the data value. `m[2]` matches the entire "data=\"...\"" part.
// We have to check `m[2]` because the data could be an empty string, which is different from "no data", in which case we return nil.
data := string(m[3])
if string(m[2]) != "" {
broadcastResult.Data = &data
}
extras := string(m[6])
if string(m[5]) != "" {
broadcastResult.Extras = &extras
}
return &broadcastResult, nil
}
// BroadcastIntentGetData broadcasts an intent with "am broadcast" and returns the result data.
func (d *Device) BroadcastIntentGetData(ctx context.Context, action string, params ...string) (string, error) {
result, err := d.BroadcastIntent(ctx, action, params...)
if err != nil {
return "", err
}
if result.Result != intentResultActivityResultOk {
if result.Data == nil {
return "", errors.Errorf("broadcast of %q failed, status = %d", action, result.Result)
}
return "", errors.Errorf("broadcast of %q failed, status = %d, data = %q", action, result.Result, *result.Data)
}
if result.Data == nil {
return "", errors.Errorf("broadcast of %q has no result data", action)
}
return *result.Data, nil
}
// BugReport returns bugreport of the device.
func (d *Device) BugReport(ctx context.Context, path string) error {
return d.Command(ctx, "bugreport", path).Run(testexec.DumpLogOnError)
}