mirror of
https://github.com/taigrr/adb.git
synced 2026-04-01 18:48:42 -07:00
- execute() no longer panics when adb is not found; returns ErrNotInstalled - Use sync.Once for lazy adb path lookup instead of init() - Fix Pull() checking src (device path) instead of dest (local path) - Add ErrDestExists error for Pull destination conflicts - Add -s serial flag to Push, Pull, Reboot, Root for multi-device support - Root() now parses stdout to determine actual success - Fix Shell() redundant variable redeclaration - Fix 'inconsostency' typo in KillServer doc - Add doc comments to all error variables - Update Go 1.26.0 -> 1.26.1 - Add tests: ConnString, ShortenSleep, GetLength, JSON roundtrip, SequenceImporter.ToInput, insertSleeps, parseDevices edge cases, parseScreenResolution edge cases, filterErr
218 lines
5.5 KiB
Go
218 lines
5.5 KiB
Go
package adb
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type Serial string
|
|
|
|
type Connection int
|
|
|
|
const (
|
|
USB Connection = iota
|
|
Network
|
|
)
|
|
|
|
// Create a Device with Connect() or a slice with Devices()
|
|
//
|
|
// Device contains the information necessary to connect to and
|
|
// communicate with a device
|
|
type Device struct {
|
|
IsAuthorized bool
|
|
SerialNo Serial
|
|
ConnType Connection
|
|
IP net.IPAddr
|
|
Port uint
|
|
FileHandle string // TODO change this to a discrete type
|
|
}
|
|
|
|
// Provides a connection string for Connect()
|
|
type ConnOptions struct {
|
|
Address net.IPAddr
|
|
Port uint
|
|
SerialNo Serial
|
|
}
|
|
|
|
// Connect to a device by IP:port.
|
|
//
|
|
// This will return a Device struct, which can be used to call other methods.
|
|
// If the connection fails or cannot complete on time, Connect will return an error.
|
|
// TODO
|
|
func Connect(ctx context.Context, opts ConnOptions) (Device, error) {
|
|
if opts.Port == 0 {
|
|
opts.Port = 5555
|
|
}
|
|
return Device{}, nil
|
|
}
|
|
|
|
func (d Device) ConnString() string {
|
|
if d.Port == 0 {
|
|
d.Port = 5555
|
|
}
|
|
return d.IP.String() + ":" + strconv.Itoa(int(d.Port))
|
|
}
|
|
|
|
// Connect to a previously discovered device.
|
|
//
|
|
// This function is helpful when connecting to a device found from the Devices call
|
|
// or when reconnecting to a previously connected device.
|
|
func (d Device) Reconnect(ctx context.Context) (Device, error) {
|
|
if d.ConnType == USB {
|
|
return d, ErrConnUSB
|
|
}
|
|
cmd := []string{"connect", d.ConnString()}
|
|
stdout, stderr, errcode, err := execute(ctx, cmd)
|
|
if err != nil {
|
|
return d, err
|
|
}
|
|
if errcode != 0 {
|
|
return d, ErrUnspecified
|
|
}
|
|
_, _ = stdout, stderr
|
|
// TODO capture and store serial number into d before returning
|
|
return d, nil
|
|
}
|
|
|
|
// Equivalent to running `adb devices`.
|
|
//
|
|
// This function returns a list of discovered devices, but note that they may not be connected.
|
|
// It is recommended to call IsConnected() against the device you're interested in using and connect
|
|
// if not already connected before proceeding.
|
|
func Devices(ctx context.Context) ([]Device, error) {
|
|
cmd := []string{"devices"}
|
|
stdout, _, errcode, err := execute(ctx, cmd)
|
|
devs := []Device{}
|
|
if err != nil {
|
|
return devs, err
|
|
}
|
|
if errcode != 0 {
|
|
return devs, ErrUnspecified
|
|
}
|
|
|
|
return parseDevices(stdout)
|
|
}
|
|
|
|
// TODO add support for connected network devices
|
|
func parseDevices(stdout string) ([]Device, error) {
|
|
devs := []Device{}
|
|
lines := strings.Split(stdout, "\n")
|
|
for _, line := range lines {
|
|
words := strings.Fields(line)
|
|
if len(words) != 2 {
|
|
continue
|
|
}
|
|
d := Device{
|
|
SerialNo: Serial(words[0]),
|
|
IsAuthorized: words[1] == "device",
|
|
}
|
|
devs = append(devs, d)
|
|
}
|
|
|
|
return devs, nil
|
|
}
|
|
|
|
// Disconnect from a device.
|
|
//
|
|
// If a device is already disconnected or otherwise not found, returns an error.
|
|
func (d Device) Disconnect(ctx context.Context) error {
|
|
if d.ConnType != Network {
|
|
return ErrConnUSB
|
|
}
|
|
_, _, _, err := execute(ctx, []string{"-s", d.ConnString(), "disconnect"})
|
|
return err
|
|
}
|
|
|
|
// KillServer kills the ADB server.
|
|
//
|
|
// Warning: this function call may cause inconsistency if not used properly.
|
|
// Killing the ADB server shouldn't ever technically be necessary, but if you do
|
|
// decide to use this function, note that it may invalidate all existing device structs.
|
|
// Older versions of Android don't play nicely with kill-server, and some may
|
|
// refuse following connection attempts if you don't disconnect from them before
|
|
// calling this function.
|
|
func KillServer(ctx context.Context) error {
|
|
_, _, _, err := execute(ctx, []string{"kill-server"})
|
|
return err
|
|
}
|
|
|
|
// Push a file to a Device.
|
|
//
|
|
// Returns an error if src does not exist or there is an error copying the file.
|
|
func (d Device) Push(ctx context.Context, src, dest string) error {
|
|
_, err := os.Stat(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, _, errcode, err := execute(ctx, []string{"-s", string(d.SerialNo), "push", src, dest})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if errcode != 0 {
|
|
return ErrUnspecified
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Pull a file from a Device.
|
|
//
|
|
// Returns an error if dest already exists or the file cannot be pulled.
|
|
func (d Device) Pull(ctx context.Context, src, dest string) error {
|
|
_, err := os.Stat(dest)
|
|
if err == nil {
|
|
return ErrDestExists
|
|
}
|
|
if !errors.Is(err, os.ErrNotExist) {
|
|
return err
|
|
}
|
|
_, _, errcode, err := execute(ctx, []string{"-s", string(d.SerialNo), "pull", src, dest})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if errcode != 0 {
|
|
return ErrUnspecified
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Reboot attempts to reboot the device.
|
|
//
|
|
// Once the device reboots, you must manually reconnect.
|
|
// Returns an error if the device cannot be contacted.
|
|
func (d Device) Reboot(ctx context.Context) error {
|
|
_, _, errcode, err := execute(ctx, []string{"-s", string(d.SerialNo), "reboot"})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if errcode != 0 {
|
|
return ErrUnspecified
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Root attempts to relaunch adb as root on the Device.
|
|
//
|
|
// Note, this may not be possible on most devices.
|
|
// Returns an error if it can't be done.
|
|
// The device connection will stay established.
|
|
// Once adb is relaunched as root, it will stay root until rebooted.
|
|
// Returns true if the device successfully relaunched as root.
|
|
func (d Device) Root(ctx context.Context) (success bool, err error) {
|
|
stdout, _, errcode, err := execute(ctx, []string{"-s", string(d.SerialNo), "root"})
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if errcode != 0 {
|
|
return false, ErrUnspecified
|
|
}
|
|
if strings.Contains(stdout, "adbd is already running as root") ||
|
|
strings.Contains(stdout, "restarting adbd as root") {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|