2016-01-15 15:54:27 +00:00
|
|
|
|
package adb
|
2015-04-12 20:34:20 +00:00
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
2015-12-28 09:08:51 +00:00
|
|
|
|
"os"
|
2015-04-12 20:34:20 +00:00
|
|
|
|
"strings"
|
2015-12-28 09:08:51 +00:00
|
|
|
|
"time"
|
2015-04-12 20:34:20 +00:00
|
|
|
|
|
2024-06-11 16:25:58 +00:00
|
|
|
|
"github.com/timoxa0/goadb/internal/errors"
|
|
|
|
|
"github.com/timoxa0/goadb/wire"
|
2015-04-12 20:34:20 +00:00
|
|
|
|
)
|
|
|
|
|
|
2015-12-28 09:08:51 +00:00
|
|
|
|
// MtimeOfClose should be passed to OpenWrite to set the file modification time to the time the Close
|
|
|
|
|
// method is called.
|
|
|
|
|
var MtimeOfClose = time.Time{}
|
|
|
|
|
|
2016-05-22 05:33:20 +00:00
|
|
|
|
// Device communicates with a specific Android device.
|
|
|
|
|
// To get an instance, call Device() on an Adb.
|
|
|
|
|
type Device struct {
|
|
|
|
|
server server
|
2015-07-11 21:32:04 +00:00
|
|
|
|
descriptor DeviceDescriptor
|
2015-11-26 19:49:29 +00:00
|
|
|
|
|
|
|
|
|
// Used to get device info.
|
|
|
|
|
deviceListFunc func() ([]*DeviceInfo, error)
|
2015-07-11 21:32:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-05-22 05:33:20 +00:00
|
|
|
|
func (c *Device) String() string {
|
2015-04-12 20:34:20 +00:00
|
|
|
|
return c.descriptor.String()
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-22 05:33:20 +00:00
|
|
|
|
// get-product is documented, but not implemented, in the server.
|
|
|
|
|
// TODO(z): Make product exported if get-product is ever implemented in adb.
|
|
|
|
|
func (c *Device) product() (string, error) {
|
2015-07-12 06:18:58 +00:00
|
|
|
|
attr, err := c.getAttribute("get-product")
|
2016-05-22 05:33:20 +00:00
|
|
|
|
return attr, wrapClientError(err, c, "Product")
|
2015-04-12 20:34:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-05-22 05:33:20 +00:00
|
|
|
|
func (c *Device) Serial() (string, error) {
|
2015-07-12 06:18:58 +00:00
|
|
|
|
attr, err := c.getAttribute("get-serialno")
|
2016-05-22 05:33:20 +00:00
|
|
|
|
return attr, wrapClientError(err, c, "Serial")
|
2015-04-12 20:34:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-05-22 05:33:20 +00:00
|
|
|
|
func (c *Device) DevicePath() (string, error) {
|
2015-07-12 06:18:58 +00:00
|
|
|
|
attr, err := c.getAttribute("get-devpath")
|
2016-05-22 05:33:20 +00:00
|
|
|
|
return attr, wrapClientError(err, c, "DevicePath")
|
2015-04-12 20:34:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-05-22 06:23:26 +00:00
|
|
|
|
func (c *Device) State() (DeviceState, error) {
|
2015-07-12 06:18:58 +00:00
|
|
|
|
attr, err := c.getAttribute("get-state")
|
2016-05-22 06:23:26 +00:00
|
|
|
|
state, err := parseDeviceState(attr)
|
|
|
|
|
return state, wrapClientError(err, c, "State")
|
2015-04-12 20:34:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-05-22 05:33:20 +00:00
|
|
|
|
func (c *Device) DeviceInfo() (*DeviceInfo, error) {
|
2015-11-26 19:49:29 +00:00
|
|
|
|
// Adb doesn't actually provide a way to get this for an individual device,
|
|
|
|
|
// so we have to just list devices and find ourselves.
|
|
|
|
|
|
2016-05-22 05:33:20 +00:00
|
|
|
|
serial, err := c.Serial()
|
2015-11-26 19:49:29 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, wrapClientError(err, c, "GetDeviceInfo(GetSerial)")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
devices, err := c.deviceListFunc()
|
|
|
|
|
if err != nil {
|
2016-05-22 05:33:20 +00:00
|
|
|
|
return nil, wrapClientError(err, c, "DeviceInfo(ListDevices)")
|
2015-11-26 19:49:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, deviceInfo := range devices {
|
|
|
|
|
if deviceInfo.Serial == serial {
|
|
|
|
|
return deviceInfo, nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-22 17:49:32 +00:00
|
|
|
|
err = errors.Errorf(errors.DeviceNotFound, "device list doesn't contain serial %s", serial)
|
2016-05-22 05:33:20 +00:00
|
|
|
|
return nil, wrapClientError(err, c, "DeviceInfo")
|
2015-11-26 19:49:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-04-12 20:34:20 +00:00
|
|
|
|
/*
|
|
|
|
|
RunCommand runs the specified commands on a shell on the device.
|
|
|
|
|
|
|
|
|
|
From the Android docs:
|
2024-06-12 11:57:38 +00:00
|
|
|
|
|
2015-04-12 20:34:20 +00:00
|
|
|
|
Run 'command arg1 arg2 ...' in a shell on the device, and return
|
|
|
|
|
its output and error streams. Note that arguments must be separated
|
|
|
|
|
by spaces. If an argument contains a space, it must be quoted with
|
|
|
|
|
double-quotes. Arguments cannot contain double quotes or things
|
|
|
|
|
will go very wrong.
|
|
|
|
|
|
|
|
|
|
Note that this is the non-interactive version of "adb shell"
|
2024-06-12 11:57:38 +00:00
|
|
|
|
|
2015-04-12 20:34:20 +00:00
|
|
|
|
Source: https://android.googlesource.com/platform/system/core/+/master/adb/SERVICES.TXT
|
|
|
|
|
|
|
|
|
|
This method quotes the arguments for you, and will return an error if any of them
|
|
|
|
|
contain double quotes.
|
|
|
|
|
*/
|
2016-05-22 05:33:20 +00:00
|
|
|
|
func (c *Device) RunCommand(cmd string, args ...string) (string, error) {
|
2015-04-12 20:34:20 +00:00
|
|
|
|
cmd, err := prepareCommandLine(cmd, args...)
|
|
|
|
|
if err != nil {
|
2015-07-12 06:18:58 +00:00
|
|
|
|
return "", wrapClientError(err, c, "RunCommand")
|
2015-04-12 20:34:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
conn, err := c.dialDevice()
|
|
|
|
|
if err != nil {
|
2015-07-12 06:18:58 +00:00
|
|
|
|
return "", wrapClientError(err, c, "RunCommand")
|
2015-04-12 20:34:20 +00:00
|
|
|
|
}
|
|
|
|
|
defer conn.Close()
|
|
|
|
|
|
|
|
|
|
req := fmt.Sprintf("shell:%s", cmd)
|
|
|
|
|
|
|
|
|
|
// Shell responses are special, they don't include a length header.
|
|
|
|
|
// We read until the stream is closed.
|
|
|
|
|
// So, we can't use conn.RoundTripSingleResponse.
|
|
|
|
|
if err = conn.SendMessage([]byte(req)); err != nil {
|
2015-07-12 06:18:58 +00:00
|
|
|
|
return "", wrapClientError(err, c, "RunCommand")
|
2015-04-12 20:34:20 +00:00
|
|
|
|
}
|
2015-12-28 23:28:53 +00:00
|
|
|
|
if _, err = conn.ReadStatus(req); err != nil {
|
2015-07-12 06:18:58 +00:00
|
|
|
|
return "", wrapClientError(err, c, "RunCommand")
|
2015-04-12 20:34:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resp, err := conn.ReadUntilEof()
|
2015-07-12 06:18:58 +00:00
|
|
|
|
return string(resp), wrapClientError(err, c, "RunCommand")
|
2015-04-12 20:34:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-06-12 11:57:38 +00:00
|
|
|
|
/*
|
|
|
|
|
Forward, from the official adb command's docs:
|
|
|
|
|
|
|
|
|
|
Asks the ADB server to forward local connections from <local>
|
|
|
|
|
to the <remote> address on a given device.
|
|
|
|
|
There, <host-prefix> can be one of the
|
|
|
|
|
host-serial/host-usb/host-local/host prefixes as described previously
|
|
|
|
|
and indicates which device/emulator to target.
|
|
|
|
|
the format of <local> is one of:
|
|
|
|
|
tcp:<port> -> TCP connection on localhost:<port>
|
|
|
|
|
local:<path> -> Unix local domain socket on <path>
|
|
|
|
|
the format of <remote> is one of:
|
|
|
|
|
tcp:<port> -> TCP localhost:<port> on device
|
|
|
|
|
local:<path> -> Unix local domain socket on device
|
|
|
|
|
jdwp:<pid> -> JDWP thread on VM process <pid>
|
|
|
|
|
vsock:<CID>:<port> -> vsock on the given CID and port
|
|
|
|
|
or even any one of the local services described below.
|
|
|
|
|
|
|
|
|
|
Source: https://android.googlesource.com/platform/packages/modules/adb/+/refs/heads/main/SERVICES.TXT
|
|
|
|
|
*/
|
|
|
|
|
func (c *Device) Forward(local string, remote string) error {
|
|
|
|
|
req := fmt.Sprintf("forward:%s;%s", local, remote)
|
|
|
|
|
_, err := c.getAttribute(req)
|
|
|
|
|
return wrapClientError(err, c, "Forward")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
KillForward, from the official adb command's docs:
|
|
|
|
|
|
|
|
|
|
Remove any existing forward local connection from <local>.
|
|
|
|
|
|
|
|
|
|
Source: https://android.googlesource.com/platform/packages/modules/adb/+/refs/heads/main/SERVICES.TXT
|
|
|
|
|
*/
|
|
|
|
|
func (c *Device) KillForward(local string) error {
|
|
|
|
|
req := fmt.Sprintf("killforward:%s", local)
|
|
|
|
|
_, err := c.getAttribute(req)
|
|
|
|
|
return wrapClientError(err, c, "KillForward")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
KillForwardAll, from the official adb command's docs:
|
|
|
|
|
|
|
|
|
|
Remove all forward network connections.
|
|
|
|
|
|
|
|
|
|
Source: https://android.googlesource.com/platform/packages/modules/adb/+/refs/heads/main/SERVICES.TXT
|
|
|
|
|
*/
|
|
|
|
|
func (c *Device) KillForwardAll() error {
|
2024-06-14 10:57:28 +00:00
|
|
|
|
req := "killforward-all"
|
2024-06-12 11:57:38 +00:00
|
|
|
|
_, err := c.getAttribute(req)
|
|
|
|
|
return wrapClientError(err, c, "KillForward")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
2024-06-14 16:47:28 +00:00
|
|
|
|
ListForwards, from the official adb command's docs:
|
2024-06-12 11:57:38 +00:00
|
|
|
|
|
2024-06-14 16:47:28 +00:00
|
|
|
|
List all existing forward connections from this server.
|
2024-06-12 11:57:38 +00:00
|
|
|
|
|
|
|
|
|
Source: https://android.googlesource.com/platform/packages/modules/adb/+/refs/heads/main/SERVICES.TXT
|
|
|
|
|
*/
|
2024-06-14 16:47:28 +00:00
|
|
|
|
func (c *Device) ListForwards() ([]Forward, error) {
|
2024-06-14 10:57:28 +00:00
|
|
|
|
req := "list-forward"
|
2024-06-12 11:57:38 +00:00
|
|
|
|
resp, err := c.getAttribute(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, wrapClientError(err, c, "ListForwards")
|
|
|
|
|
}
|
|
|
|
|
forwards, err := parseForward(resp, c.descriptor.serial)
|
|
|
|
|
return forwards, wrapClientError(err, c, "ListForwards")
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-12 20:34:20 +00:00
|
|
|
|
/*
|
2016-05-22 05:33:20 +00:00
|
|
|
|
Remount, from the official adb command’s docs:
|
2024-06-12 11:57:38 +00:00
|
|
|
|
|
2015-04-12 20:34:20 +00:00
|
|
|
|
Ask adbd to remount the device's filesystem in read-write mode,
|
|
|
|
|
instead of read-only. This is usually necessary before performing
|
|
|
|
|
an "adb sync" or "adb push" request.
|
|
|
|
|
This request may not succeed on certain builds which do not allow
|
|
|
|
|
that.
|
2024-06-12 11:57:38 +00:00
|
|
|
|
|
2015-04-12 20:34:20 +00:00
|
|
|
|
Source: https://android.googlesource.com/platform/system/core/+/master/adb/SERVICES.TXT
|
|
|
|
|
*/
|
2016-05-22 05:33:20 +00:00
|
|
|
|
func (c *Device) Remount() (string, error) {
|
2015-04-12 20:34:20 +00:00
|
|
|
|
conn, err := c.dialDevice()
|
|
|
|
|
if err != nil {
|
2015-07-12 06:18:58 +00:00
|
|
|
|
return "", wrapClientError(err, c, "Remount")
|
2015-04-12 20:34:20 +00:00
|
|
|
|
}
|
|
|
|
|
defer conn.Close()
|
|
|
|
|
|
|
|
|
|
resp, err := conn.RoundTripSingleResponse([]byte("remount"))
|
2015-07-12 06:18:58 +00:00
|
|
|
|
return string(resp), wrapClientError(err, c, "Remount")
|
2015-04-12 20:34:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-05-22 05:33:20 +00:00
|
|
|
|
func (c *Device) ListDirEntries(path string) (*DirEntries, error) {
|
2015-04-12 20:34:20 +00:00
|
|
|
|
conn, err := c.getSyncConn()
|
|
|
|
|
if err != nil {
|
2015-07-12 06:18:58 +00:00
|
|
|
|
return nil, wrapClientError(err, c, "ListDirEntries(%s)", path)
|
2015-04-12 20:34:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-07-12 06:18:58 +00:00
|
|
|
|
entries, err := listDirEntries(conn, path)
|
|
|
|
|
return entries, wrapClientError(err, c, "ListDirEntries(%s)", path)
|
2015-04-12 20:34:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-05-22 05:33:20 +00:00
|
|
|
|
func (c *Device) Stat(path string) (*DirEntry, error) {
|
2015-04-12 20:34:20 +00:00
|
|
|
|
conn, err := c.getSyncConn()
|
|
|
|
|
if err != nil {
|
2015-07-12 06:18:58 +00:00
|
|
|
|
return nil, wrapClientError(err, c, "Stat(%s)", path)
|
2015-04-12 20:34:20 +00:00
|
|
|
|
}
|
2015-09-06 03:37:40 +00:00
|
|
|
|
defer conn.Close()
|
2015-04-12 20:34:20 +00:00
|
|
|
|
|
2015-07-12 06:18:58 +00:00
|
|
|
|
entry, err := stat(conn, path)
|
|
|
|
|
return entry, wrapClientError(err, c, "Stat(%s)", path)
|
2015-04-12 20:34:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-05-22 05:33:20 +00:00
|
|
|
|
func (c *Device) OpenRead(path string) (io.ReadCloser, error) {
|
2015-04-12 20:34:20 +00:00
|
|
|
|
conn, err := c.getSyncConn()
|
|
|
|
|
if err != nil {
|
2015-07-12 06:18:58 +00:00
|
|
|
|
return nil, wrapClientError(err, c, "OpenRead(%s)", path)
|
2015-04-12 20:34:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-07-12 06:18:58 +00:00
|
|
|
|
reader, err := receiveFile(conn, path)
|
|
|
|
|
return reader, wrapClientError(err, c, "OpenRead(%s)", path)
|
2015-04-12 20:34:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-28 09:08:51 +00:00
|
|
|
|
// OpenWrite opens the file at path on the device, creating it with the permissions specified
|
|
|
|
|
// by perms if necessary, and returns a writer that writes to the file.
|
|
|
|
|
// The files modification time will be set to mtime when the WriterCloser is closed. The zero value
|
|
|
|
|
// is TimeOfClose, which will use the time the Close method is called as the modification time.
|
2016-05-22 05:33:20 +00:00
|
|
|
|
func (c *Device) OpenWrite(path string, perms os.FileMode, mtime time.Time) (io.WriteCloser, error) {
|
2015-12-28 09:08:51 +00:00
|
|
|
|
conn, err := c.getSyncConn()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, wrapClientError(err, c, "OpenWrite(%s)", path)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
writer, err := sendFile(conn, path, perms, mtime)
|
|
|
|
|
return writer, wrapClientError(err, c, "OpenWrite(%s)", path)
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-12 20:34:20 +00:00
|
|
|
|
// getAttribute returns the first message returned by the server by running
|
|
|
|
|
// <host-prefix>:<attr>, where host-prefix is determined from the DeviceDescriptor.
|
2016-05-22 05:33:20 +00:00
|
|
|
|
func (c *Device) getAttribute(attr string) (string, error) {
|
2016-01-10 21:33:22 +00:00
|
|
|
|
resp, err := roundTripSingleResponse(c.server,
|
2015-04-12 20:34:20 +00:00
|
|
|
|
fmt.Sprintf("%s:%s", c.descriptor.getHostPrefix(), attr))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
return string(resp), nil
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-22 05:33:20 +00:00
|
|
|
|
func (c *Device) getSyncConn() (*wire.SyncConn, error) {
|
2015-07-12 06:18:58 +00:00
|
|
|
|
conn, err := c.dialDevice()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Switch the connection to sync mode.
|
|
|
|
|
if err := wire.SendMessageString(conn, "sync:"); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2015-12-28 23:28:53 +00:00
|
|
|
|
if _, err := conn.ReadStatus("sync"); err != nil {
|
2015-07-12 06:18:58 +00:00
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return conn.NewSyncConn(), nil
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-12 20:34:20 +00:00
|
|
|
|
// dialDevice switches the connection to communicate directly with the device
|
|
|
|
|
// by requesting the transport defined by the DeviceDescriptor.
|
2016-05-22 05:33:20 +00:00
|
|
|
|
func (c *Device) dialDevice() (*wire.Conn, error) {
|
2016-01-10 21:33:22 +00:00
|
|
|
|
conn, err := c.server.Dial()
|
2015-04-12 20:34:20 +00:00
|
|
|
|
if err != nil {
|
2015-07-12 06:18:58 +00:00
|
|
|
|
return nil, err
|
2015-04-12 20:34:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
req := fmt.Sprintf("host:%s", c.descriptor.getTransportDescriptor())
|
|
|
|
|
if err = wire.SendMessageString(conn, req); err != nil {
|
|
|
|
|
conn.Close()
|
2016-05-22 17:49:32 +00:00
|
|
|
|
return nil, errors.WrapErrf(err, "error connecting to device '%s'", c.descriptor)
|
2015-04-12 20:34:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-28 23:28:53 +00:00
|
|
|
|
if _, err = conn.ReadStatus(req); err != nil {
|
2015-04-12 20:34:20 +00:00
|
|
|
|
conn.Close()
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return conn, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// prepareCommandLine validates the command and argument strings, quotes
|
|
|
|
|
// arguments if required, and joins them into a valid adb command string.
|
|
|
|
|
func prepareCommandLine(cmd string, args ...string) (string, error) {
|
|
|
|
|
if isBlank(cmd) {
|
2016-05-22 17:49:32 +00:00
|
|
|
|
return "", errors.AssertionErrorf("command cannot be empty")
|
2015-04-12 20:34:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i, arg := range args {
|
|
|
|
|
if strings.ContainsRune(arg, '"') {
|
2016-05-22 17:49:32 +00:00
|
|
|
|
return "", errors.Errorf(errors.ParseError, "arg at index %d contains an invalid double quote: %s", i, arg)
|
2015-04-12 20:34:20 +00:00
|
|
|
|
}
|
|
|
|
|
if containsWhitespace(arg) {
|
|
|
|
|
args[i] = fmt.Sprintf("\"%s\"", arg)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-11 21:32:04 +00:00
|
|
|
|
// Prepend the command to the args array.
|
2015-04-12 20:34:20 +00:00
|
|
|
|
if len(args) > 0 {
|
|
|
|
|
cmd = fmt.Sprintf("%s %s", cmd, strings.Join(args, " "))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cmd, nil
|
|
|
|
|
}
|