-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
adb.go
218 lines (199 loc) · 5.64 KB
/
adb.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
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
}
// Kill the ADB Server
//
// Warning, this function call may cause inconsostency 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
}
stdout, stderr, errcode, err := execute(ctx, []string{"push", src, dest})
if err != nil {
return err
}
if errcode != 0 {
return ErrUnspecified
}
_, _ = stdout, stderr
// TODO check the return strings of the output to determine if the file copy succeeded
return nil
}
// Pulls a file from a Device
//
// Returns an error if src does not exist, or if dest already exists or cannot be created
func (d Device) Pull(ctx context.Context, src, dest string) error {
_, err := os.Stat(src)
if !errors.Is(err, os.ErrNotExist) {
return err
}
stdout, stderr, errcode, err := execute(ctx, []string{"pull", src, dest})
if err != nil {
return err
}
if errcode != 0 {
return ErrUnspecified
}
_, _ = stdout, stderr
// TODO check the return strings of the output to determine if the file copy succeeded
return nil
}
// 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 {
stdout, stderr, errcode, err := execute(ctx, []string{"reboot"})
if err != nil {
return err
}
if errcode != 0 {
return ErrUnspecified
}
_, _ = stdout, stderr
// TODO check the return strings of the output to determine if the file copy succeeded
return nil
}
// Attempt 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, stderr, errcode, err := execute(ctx, []string{"root"})
if err != nil {
return false, err
}
if errcode != 0 {
return false, ErrUnspecified
}
_, _ = stdout, stderr
// TODO check the return strings of the output to determine if the file copy succeeded
return true, nil
}