mirror of
https://github.com/httprunner/httprunner.git
synced 2026-07-12 16:01:27 +08:00
refactor: move hrp/ to root folder
This commit is contained in:
727
pkg/gadb/device.go
Normal file
727
pkg/gadb/device.go
Normal file
@@ -0,0 +1,727 @@
|
||||
package gadb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/httprunner/v5/code"
|
||||
"github.com/httprunner/httprunner/v5/internal/builtin"
|
||||
)
|
||||
|
||||
type DeviceFileInfo struct {
|
||||
Name string
|
||||
Mode os.FileMode
|
||||
Size uint32
|
||||
LastModified time.Time
|
||||
}
|
||||
|
||||
func (info DeviceFileInfo) IsDir() bool {
|
||||
return (info.Mode & (1 << 14)) == (1 << 14)
|
||||
}
|
||||
|
||||
const DefaultFileMode = os.FileMode(0o664)
|
||||
|
||||
type DeviceState string
|
||||
|
||||
const (
|
||||
StateUnknown DeviceState = "UNKNOWN"
|
||||
StateOnline DeviceState = "online"
|
||||
StateOffline DeviceState = "offline"
|
||||
StateDisconnected DeviceState = "disconnected"
|
||||
StateBootloader DeviceState = "bootloader"
|
||||
StateRecovery DeviceState = "recovery"
|
||||
StateUnauthorized DeviceState = "unauthorized"
|
||||
)
|
||||
|
||||
var deviceStateStrings = map[string]DeviceState{
|
||||
"": StateDisconnected, // no devices/emulators found
|
||||
"offline": StateOffline,
|
||||
"bootloader": StateBootloader,
|
||||
"recovery": StateRecovery,
|
||||
"unauthorized": StateUnauthorized,
|
||||
"device": StateOnline,
|
||||
}
|
||||
|
||||
func deviceStateConv(k string) (deviceState DeviceState) {
|
||||
var ok bool
|
||||
if deviceState, ok = deviceStateStrings[k]; !ok {
|
||||
return StateUnknown
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type DeviceForward struct {
|
||||
Serial string
|
||||
Local string
|
||||
Remote string
|
||||
Reverse bool
|
||||
// LocalProtocol string
|
||||
// RemoteProtocol string
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
adbClient Client
|
||||
serial string
|
||||
attrs map[string]string
|
||||
feat Features
|
||||
}
|
||||
|
||||
func (d *Device) HasFeature(name Feature) bool {
|
||||
feats, err := d.GetFeatures()
|
||||
if err != nil || len(feats) == 0 {
|
||||
return false
|
||||
}
|
||||
return feats.HasFeature(name)
|
||||
}
|
||||
|
||||
func (d *Device) GetFeatures() (features Features, err error) {
|
||||
if len(d.feat) > 0 {
|
||||
return d.feat, nil
|
||||
}
|
||||
return d.features()
|
||||
}
|
||||
|
||||
func (d *Device) features() (features Features, err error) {
|
||||
res, err := d.executeCommand("host:features")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(res) > 4 {
|
||||
// stip hash
|
||||
res = res[4:]
|
||||
}
|
||||
fs := strings.Split(string(res), ",")
|
||||
features = make(Features, len(fs))
|
||||
for _, f := range fs {
|
||||
features[Feature(f)] = struct{}{}
|
||||
}
|
||||
d.feat = features
|
||||
return features, nil
|
||||
}
|
||||
|
||||
func (d *Device) HasAttribute(key string) bool {
|
||||
_, ok := d.attrs[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (d *Device) Product() (string, error) {
|
||||
if d.HasAttribute("product") {
|
||||
return d.attrs["product"], nil
|
||||
}
|
||||
return "", errors.New("does not have attribute: product")
|
||||
}
|
||||
|
||||
func (d *Device) Model() (string, error) {
|
||||
if d.HasAttribute("model") {
|
||||
return d.attrs["model"], nil
|
||||
}
|
||||
return "", errors.New("does not have attribute: model")
|
||||
}
|
||||
|
||||
func (d *Device) Brand() (string, error) {
|
||||
if d.HasAttribute("brand") {
|
||||
return d.attrs["brand"], nil
|
||||
}
|
||||
brand, err := d.RunShellCommand("getprop", "ro.product.brand")
|
||||
brand = strings.TrimSpace(brand)
|
||||
if err != nil {
|
||||
return "", errors.New("does not have attribute: brand")
|
||||
}
|
||||
d.attrs["brand"] = brand
|
||||
return brand, nil
|
||||
}
|
||||
|
||||
func (d *Device) Usb() (string, error) {
|
||||
if d.HasAttribute("usb") {
|
||||
return d.attrs["usb"], nil
|
||||
}
|
||||
return "", errors.New("does not have attribute: usb")
|
||||
}
|
||||
|
||||
func (d *Device) transportId() (string, error) {
|
||||
if d.HasAttribute("transport_id") {
|
||||
return d.attrs["transport_id"], nil
|
||||
}
|
||||
return "", errors.New("does not have attribute: transport_id")
|
||||
}
|
||||
|
||||
func (d *Device) DeviceInfo() map[string]string {
|
||||
return d.attrs
|
||||
}
|
||||
|
||||
func (d *Device) Serial() string {
|
||||
// resp, err := d.adbClient.executeCommand(fmt.Sprintf("host-serial:%s:get-serialno", d.serial))
|
||||
return d.serial
|
||||
}
|
||||
|
||||
func (d *Device) IsUsb() (bool, error) {
|
||||
usb, err := d.Usb()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return usb != "", nil
|
||||
}
|
||||
|
||||
func (d *Device) State() (DeviceState, error) {
|
||||
resp, err := d.adbClient.executeCommand(fmt.Sprintf("host-serial:%s:get-state", d.serial))
|
||||
return deviceStateConv(resp), err
|
||||
}
|
||||
|
||||
func (d *Device) DevicePath() (string, error) {
|
||||
resp, err := d.adbClient.executeCommand(fmt.Sprintf("host-serial:%s:get-devpath", d.serial))
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (d *Device) Forward(remoteInterface interface{}, noRebind ...bool) (port int, err error) {
|
||||
var remote string
|
||||
switch r := remoteInterface.(type) {
|
||||
// for unix sockets
|
||||
case string:
|
||||
remote = r
|
||||
case int:
|
||||
remote = fmt.Sprintf("tcp:%d", r)
|
||||
}
|
||||
|
||||
forwardList, err := d.ForwardList()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, forwardItem := range forwardList {
|
||||
if forwardItem.Remote == remote {
|
||||
return strconv.Atoi(forwardItem.Local[4:])
|
||||
}
|
||||
}
|
||||
localPort, err := builtin.GetFreePort()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
local := fmt.Sprintf("tcp:%d", localPort)
|
||||
|
||||
command := fmt.Sprintf("host-serial:%s:forward:%s;%s", d.serial, local, remote)
|
||||
if len(noRebind) != 0 && noRebind[0] {
|
||||
command = fmt.Sprintf("host-serial:%s:forward:norebind:%s;%s", d.serial, local, remote)
|
||||
}
|
||||
|
||||
_, err = d.adbClient.executeCommand(command, true)
|
||||
return localPort, nil
|
||||
}
|
||||
|
||||
func (d *Device) ForwardList() (deviceForwardList []DeviceForward, err error) {
|
||||
var forwardList []DeviceForward
|
||||
if forwardList, err = d.adbClient.ForwardList(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deviceForwardList = make([]DeviceForward, 0, len(deviceForwardList))
|
||||
for i := range forwardList {
|
||||
if forwardList[i].Serial == d.serial {
|
||||
deviceForwardList = append(deviceForwardList, forwardList[i])
|
||||
}
|
||||
}
|
||||
// resp, err := d.adbClient.executeCommand(fmt.Sprintf("host-serial:%s:list-forward", d.serial))
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) ForwardKill(localPort int) (err error) {
|
||||
local := fmt.Sprintf("tcp:%d", localPort)
|
||||
_, err = d.adbClient.executeCommand(fmt.Sprintf("host-serial:%s:killforward:%s", d.serial, local), true)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) ReverseForward(localPort int, remoteInterface interface{}, noRebind ...bool) (err error) {
|
||||
var command string
|
||||
var remote string
|
||||
local := fmt.Sprintf("tcp:%d", localPort)
|
||||
switch r := remoteInterface.(type) {
|
||||
// for unix sockets
|
||||
case string:
|
||||
remote = r
|
||||
case int:
|
||||
remote = fmt.Sprintf("tcp:%d", r)
|
||||
}
|
||||
|
||||
if len(noRebind) != 0 && noRebind[0] {
|
||||
command = fmt.Sprintf("reverse:forward:norebind:%s;%s", remote, local)
|
||||
} else {
|
||||
command = fmt.Sprintf("reverse:forward:%s;%s", remote, local)
|
||||
}
|
||||
_, err = d.executeCommand(command, true)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) ReverseForwardList() (deviceForwardList []DeviceForward, err error) {
|
||||
res, err := d.executeCommand("reverse:list-forward")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resStr := string(res)
|
||||
lines := strings.Split(resStr, "\n")
|
||||
for _, line := range lines {
|
||||
groups := strings.Split(line, " ")
|
||||
if len(groups) == 3 {
|
||||
deviceForwardList = append(deviceForwardList, DeviceForward{
|
||||
Reverse: true,
|
||||
Serial: d.serial,
|
||||
Remote: groups[1],
|
||||
Local: groups[2],
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) ReverseForwardKill(remoteInterface interface{}) error {
|
||||
remote := ""
|
||||
switch r := remoteInterface.(type) {
|
||||
case string:
|
||||
remote = r
|
||||
case int:
|
||||
remote = fmt.Sprintf("tcp:%d", r)
|
||||
}
|
||||
_, err := d.executeCommand(fmt.Sprintf("reverse:killforward:%s", remote), true)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Device) RunStubCommand(command []byte, processName string) (res string, err error) {
|
||||
var tp transport
|
||||
if tp, err = d.createDeviceTransport(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() { _ = tp.Close() }()
|
||||
|
||||
if err = tp.SendWithCheck(fmt.Sprintf("localabstract:%s", processName)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = tp.SendBytes(command); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
lenBuf, err := tp.ReadBytesN(4)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
length := binary.LittleEndian.Uint32(lenBuf)
|
||||
result, err := tp.ReadBytesN(int(length) - 4)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(result), nil
|
||||
}
|
||||
|
||||
func (d *Device) ReverseForwardKillAll() error {
|
||||
_, err := d.executeCommand("reverse:killforward-all")
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Device) RunShellCommand(cmd string, args ...string) (string, error) {
|
||||
raw, err := d.RunShellCommandWithBytes(cmd, args...)
|
||||
if err != nil && errors.Cause(err) == nil {
|
||||
err = errors.Wrap(code.DeviceShellExecError, err.Error())
|
||||
}
|
||||
return string(raw), err
|
||||
}
|
||||
|
||||
func (d *Device) RunShellCommandWithBytes(cmd string, args ...string) ([]byte, error) {
|
||||
if d.HasFeature(FeatShellV2) {
|
||||
return d.RunShellCommandV2WithBytes(cmd, args...)
|
||||
}
|
||||
if len(args) > 0 {
|
||||
cmd = fmt.Sprintf("%s %s", cmd, strings.Join(args, " "))
|
||||
}
|
||||
if strings.TrimSpace(cmd) == "" {
|
||||
return nil, errors.New("adb shell: command cannot be empty")
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
// log elapsed seconds for shell execution
|
||||
log.Debug().Str("cmd",
|
||||
fmt.Sprintf("adb -s %s shell %s", d.serial, cmd)).
|
||||
Int64("elapsed(ms)", time.Since(startTime).Milliseconds()).
|
||||
Msg("run adb shell")
|
||||
}()
|
||||
|
||||
raw, err := d.executeCommand(fmt.Sprintf("shell:%s", cmd))
|
||||
return raw, err
|
||||
}
|
||||
|
||||
// RunShellCommandV2WithBytes shell v2, 支持后台运行而不会阻断
|
||||
func (d *Device) RunShellCommandV2WithBytes(cmd string, args ...string) ([]byte, error) {
|
||||
if len(args) > 0 {
|
||||
cmd = fmt.Sprintf("%s %s", cmd, strings.Join(args, " "))
|
||||
}
|
||||
if strings.TrimSpace(cmd) == "" {
|
||||
return nil, errors.New("adb shell: command cannot be empty")
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
// log elapsed seconds for shell execution
|
||||
log.Debug().Str("cmd",
|
||||
fmt.Sprintf("adb -s %s shell %s", d.serial, cmd)).
|
||||
Int64("elapsed(ms)", time.Since(startTime).Milliseconds()).
|
||||
Msg("run adb shell in v2")
|
||||
}()
|
||||
|
||||
raw, err := d.executeCommand(fmt.Sprintf("shell,v2,raw:%s", cmd))
|
||||
if err != nil {
|
||||
return raw, err
|
||||
}
|
||||
return d.parseV2CommandWithBytes(raw)
|
||||
}
|
||||
|
||||
func (d *Device) parseV2CommandWithBytes(input []byte) (res []byte, err error) {
|
||||
if len(input) == 0 {
|
||||
return input, nil
|
||||
}
|
||||
reader := bytes.NewReader(input)
|
||||
sizeBuf := make([]byte, 4)
|
||||
var (
|
||||
resBuf []byte
|
||||
exitCode int
|
||||
)
|
||||
loop:
|
||||
for {
|
||||
msgCode, err := reader.ReadByte()
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
switch msgCode {
|
||||
case 0x01, 0x02: // STDOUT, STDERR
|
||||
_, err = io.ReadFull(reader, sizeBuf)
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
size := binary.LittleEndian.Uint32(sizeBuf)
|
||||
if cap(resBuf) < int(size) {
|
||||
resBuf = make([]byte, int(size))
|
||||
}
|
||||
_, err = io.ReadFull(reader, resBuf[:size])
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
res = append(res, resBuf[:size]...)
|
||||
case 0x03: // EXIT
|
||||
_, err = io.ReadFull(reader, sizeBuf)
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
size := binary.LittleEndian.Uint32(sizeBuf)
|
||||
if cap(resBuf) < int(size) {
|
||||
resBuf = make([]byte, int(size))
|
||||
}
|
||||
ec, err := reader.ReadByte()
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
exitCode = int(ec)
|
||||
break loop
|
||||
default:
|
||||
return input, nil
|
||||
}
|
||||
}
|
||||
if exitCode != 0 {
|
||||
return nil, errors.New(string(res))
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (d *Device) EnableAdbOverTCP(port ...int) (err error) {
|
||||
if len(port) == 0 {
|
||||
port = []int{AdbDaemonPort}
|
||||
}
|
||||
|
||||
_, err = d.executeCommand(fmt.Sprintf("tcpip:%d", port[0]), true)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) createDeviceTransport(readTimeout ...time.Duration) (tp transport, err error) {
|
||||
if tp, err = newTransport(fmt.Sprintf("%s:%d", d.adbClient.host, d.adbClient.port), readTimeout...); err != nil {
|
||||
return transport{}, err
|
||||
}
|
||||
|
||||
err = tp.SendWithCheck(fmt.Sprintf("host:transport:%s", d.serial))
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) executeCommand(command string, onlyVerifyResponse ...bool) (raw []byte, err error) {
|
||||
if len(onlyVerifyResponse) == 0 {
|
||||
onlyVerifyResponse = []bool{false}
|
||||
}
|
||||
|
||||
var tp transport
|
||||
if tp, err = d.createDeviceTransport(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = tp.Close() }()
|
||||
|
||||
if err = tp.SendWithCheck(command); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if onlyVerifyResponse[0] {
|
||||
return
|
||||
}
|
||||
|
||||
raw, err = tp.ReadBytesAll()
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) List(remotePath string) (devFileInfos []DeviceFileInfo, err error) {
|
||||
var tp transport
|
||||
if tp, err = d.createDeviceTransport(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = tp.Close() }()
|
||||
|
||||
var sync syncTransport
|
||||
if sync, err = tp.CreateSyncTransport(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = sync.Close() }()
|
||||
|
||||
if err = sync.Send("LIST", remotePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
devFileInfos = make([]DeviceFileInfo, 0)
|
||||
|
||||
var entry DeviceFileInfo
|
||||
for entry, err = sync.ReadDirectoryEntry(); err == nil; entry, err = sync.ReadDirectoryEntry() {
|
||||
if entry == (DeviceFileInfo{}) {
|
||||
break
|
||||
}
|
||||
devFileInfos = append(devFileInfos, entry)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) PushFile(localPath, remotePath string, modification ...time.Time) (err error) {
|
||||
localFile, err := os.Open(localPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer localFile.Close()
|
||||
|
||||
if len(modification) == 0 {
|
||||
var stat os.FileInfo
|
||||
if stat, err = localFile.Stat(); err != nil {
|
||||
return err
|
||||
}
|
||||
modification = []time.Time{stat.ModTime()}
|
||||
}
|
||||
|
||||
return d.Push(localFile, remotePath, modification[0], DefaultFileMode)
|
||||
}
|
||||
|
||||
func (d *Device) Push(source io.Reader, remotePath string, modification time.Time, mode ...os.FileMode) (err error) {
|
||||
if len(mode) == 0 {
|
||||
mode = []os.FileMode{DefaultFileMode}
|
||||
}
|
||||
|
||||
var tp transport
|
||||
if tp, err = d.createDeviceTransport(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tp.Close() }()
|
||||
|
||||
var sync syncTransport
|
||||
if sync, err = tp.CreateSyncTransport(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = sync.Close() }()
|
||||
|
||||
data := fmt.Sprintf("%s,%d", remotePath, mode[0])
|
||||
if err = sync.Send("SEND", data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = sync.SendStream(source); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = sync.SendStatus("DONE", uint32(modification.Unix())); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = sync.VerifyStatus(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) Pull(remotePath string, dest io.Writer) (err error) {
|
||||
var tp transport
|
||||
if tp, err = d.createDeviceTransport(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tp.Close() }()
|
||||
|
||||
var sync syncTransport
|
||||
if sync, err = tp.CreateSyncTransport(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = sync.Close() }()
|
||||
|
||||
if err = sync.Send("RECV", remotePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = sync.WriteStream(dest)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) installViaABBExec(apk io.ReadSeeker, args ...string) (raw []byte, err error) {
|
||||
var (
|
||||
tp transport
|
||||
filesize int64
|
||||
)
|
||||
filesize, err = apk.Seek(0, io.SeekEnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tp, err = d.createDeviceTransport(5 * time.Minute); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = tp.Close() }()
|
||||
cmd := "abb_exec:package\x00install\x00-t"
|
||||
for _, arg := range args {
|
||||
cmd += "\x00" + arg
|
||||
}
|
||||
cmd += fmt.Sprintf("\x00-S\x00%d", filesize)
|
||||
if err = tp.SendWithCheck(cmd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = apk.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = io.Copy(tp.Conn(), apk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
raw, err = tp.ReadBytesAll()
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) InstallAPK(apkPath string, args ...string) (string, error) {
|
||||
apkFile, err := os.Open(apkPath)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, fmt.Sprintf("open apk file %s failed", apkPath))
|
||||
}
|
||||
defer apkFile.Close()
|
||||
|
||||
haserr := func(ret string) bool {
|
||||
return strings.Contains(ret, "Failure")
|
||||
}
|
||||
if d.HasFeature(FeatAbbExec) {
|
||||
raw, err := d.installViaABBExec(apkFile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error installing: %v", err)
|
||||
}
|
||||
if haserr(string(raw)) {
|
||||
return "", errors.New(string(raw))
|
||||
}
|
||||
return string(raw), err
|
||||
}
|
||||
|
||||
remote := fmt.Sprintf("/data/local/tmp/%s.apk", builtin.GenNameWithTimestamp("gadb_remote_%d"))
|
||||
err = d.Push(apkFile, remote, time.Now())
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error pushing: %v", err)
|
||||
}
|
||||
args = append([]string{"install"}, args...)
|
||||
args = append(args, "-f", remote)
|
||||
res, err := d.RunShellCommand("pm", args...)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "install apk failed")
|
||||
}
|
||||
if haserr(res) {
|
||||
return "", errors.New(res)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (d *Device) Uninstall(packageName string, keepData ...bool) (string, error) {
|
||||
if len(keepData) == 0 {
|
||||
keepData = []bool{false}
|
||||
}
|
||||
packageName = strings.TrimSpace(packageName)
|
||||
if len(packageName) == 0 {
|
||||
return "", fmt.Errorf("invalid package name")
|
||||
}
|
||||
args := []string{"uninstall"}
|
||||
if keepData[0] {
|
||||
args = append(args, "-k")
|
||||
}
|
||||
args = append(args, packageName)
|
||||
return d.RunShellCommand("pm", args...)
|
||||
}
|
||||
|
||||
func (d *Device) ListPackages() ([]string, error) {
|
||||
args := []string{"list", "packages"}
|
||||
resRaw, err := d.RunShellCommand("pm", args...)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
lines := strings.Split(resRaw, "\n")
|
||||
var packages []string
|
||||
for _, line := range lines {
|
||||
packageName := strings.TrimPrefix(line, "package:")
|
||||
packages = append(packages, packageName)
|
||||
}
|
||||
return packages, nil
|
||||
}
|
||||
|
||||
func (d *Device) IsPackageInstalled(packageName string) bool {
|
||||
packages, err := d.ListPackages()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
packageName = strings.TrimSpace(packageName)
|
||||
if len(packageName) == 0 {
|
||||
return false
|
||||
}
|
||||
return builtin.Contains(packages, packageName)
|
||||
}
|
||||
|
||||
func (d *Device) IsPackageRunning(packageName string) bool {
|
||||
output, err := d.RunShellCommand("pidof", packageName)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return strings.TrimSpace(output) != ""
|
||||
}
|
||||
|
||||
func (d *Device) ScreenCap() ([]byte, error) {
|
||||
if d.HasFeature(FeatShellV2) {
|
||||
return d.RunShellCommandV2WithBytes("screencap", "-p")
|
||||
}
|
||||
|
||||
// for shell v1, screenshot buffer maybe truncated
|
||||
// thus we firstly save it to local file and then pull it
|
||||
tempPath := fmt.Sprintf("/data/local/tmp/screenshot_%d.png",
|
||||
time.Now().Unix())
|
||||
_, err := d.RunShellCommandWithBytes("screencap", "-p", tempPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
err = d.Pull(tempPath, buffer)
|
||||
return buffer.Bytes(), err
|
||||
}
|
||||
Reference in New Issue
Block a user