blob: f1f0926534a489d5b9fc02a0934e46e3417745b1 [file] [log] [blame]
// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package adb provides an interface to the Android Debug Bridge.
package adb
import (
"bytes"
"errors"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"android.googlesource.com/platform/tools/gpu/maker"
)
// ErrADBNotFound is returned when the ADB executable is not found.
var ErrADBNotFound = errors.New("ADB command not found on PATH")
// ErrDeviceUnauthorized is returned by ADB commands when the device has not
// authorized ADB debugging. Check the confirmation dialog on the device.
var ErrDeviceUnauthorized = errors.New("Device unauthorized")
// The path to the adb executable, or an empty string if the adb executable was
// not found.
var adb string
func init() {
// Search for ADB using ANDROID_HOME
if home := os.Getenv("ANDROID_HOME"); home != "" {
path, err := filepath.Abs(filepath.Join(home, "platform-tools", "adb") + maker.HostExecutableExtension)
if err == nil {
if _, err := os.Stat(path); err == nil {
adb = path
return
}
}
}
// Fallback to searching on PATH.
if p, err := exec.LookPath("adb"); err == nil {
if p, err = filepath.Abs(p); err == nil {
adb = p
}
}
}
// Cmd represents a command that can be run on an Android device.
type Cmd struct {
// Path is the path of the command to run on the device.
//
// If the string is empty, the command is treated as a ADB command for Device.
Path string
// Args holds the command line arguments to pass to the command.
Args []string
// The device this command should be run on. If nil, then any one of the
// attached devices will execute the command.
Device *Device
// Stdout and Stderr specify the process's standard output and error.
//
// If either is nil, Run connects the corresponding file descriptor
// to the null device (os.DevNull).
//
// If Stdout and Stderr are the same writer, at most one
// goroutine at a time will call Write.
Stdout io.Writer
Stderr io.Writer
}
// Run starts the specified command and waits for it to complete.
// The returned error is nil if the command runs, has no problems copying
// stdout and stderr, and exits with a zero exit status.
func (c *Cmd) Run() error {
args := []string{}
if c.Device != nil {
args = append(args, "-s", c.Device.Serial)
}
if c.Path != "" {
args = append(args, "shell", c.Path)
}
args = append(args, c.Args...)
cmd := exec.Command(adb, args...)
cmd.Stdout = c.Stdout
cmd.Stderr = c.Stderr
return cmd.Run()
}
// Call starts the specified command and waits for it to complete, returning the
// all stdout as a string.
// The returned error is nil if the command runs, has no problems copying
// stdout and stderr, and exits with a zero exit status.
func (c *Cmd) Call() (string, error) {
clone := *c // Don't change c's Stdout
stdout := &bytes.Buffer{}
if clone.Stdout != nil {
clone.Stdout = io.MultiWriter(clone.Stdout, stdout)
} else {
clone.Stdout = stdout
}
stderr := &bytes.Buffer{}
if clone.Stdout != nil {
clone.Stderr = io.MultiWriter(clone.Stdout, stderr)
} else {
clone.Stderr = stderr
}
err := clone.Run()
if err != nil && strings.Contains(stderr.String(), "error: device unauthorized.") {
err = ErrDeviceUnauthorized
}
return stdout.String(), err
}