Skip to content

Commit a9c9b59

Browse files
committed
add install and fix screenshot on lower device
1 parent 98232d1 commit a9c9b59

File tree

6 files changed

+367
-58
lines changed

6 files changed

+367
-58
lines changed

README.md

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
## Features
77
- [x] show device selection when multi device connected
88
- [x] screenshot
9-
- [ ] install support http url
10-
- [ ] support launch after install apk
9+
- [x] install support http url
10+
- [x] support launch after install apk
1111
- [ ] show wlan (ip,mac,signal), enable and disable it
1212

1313
## Install
@@ -22,16 +22,57 @@ brew install codeskyblue/tap/fa
2222
download binary from [**releases**](https://github.com/codeskyblue/fa/releases)
2323

2424
## Usage
25-
Screenshot
25+
Show version
26+
27+
```bash
28+
$ fa version
29+
fa version v0.0.5 # just example
30+
```
31+
32+
Screenshot (only png support for now)
2633

2734
```bash
2835
fa screenshot -o screenshot.png
2936
```
3037

31-
~~Install APK~~
38+
Install APK
39+
40+
```
41+
$ fa install ApiDemos-debug.apk
42+
```
43+
44+
Install APK then start app
45+
46+
```
47+
$ fa install --launch ApiDemos-debug.apk
48+
```
49+
50+
Install APK from URL with _uninstall first and launch after installed_
51+
52+
53+
```
54+
$ fa install --force --launch https://github.com/appium/java-client/raw/master/src/test/java/io/appium/java_client/ApiDemos-debug.apk
55+
Downloading ApiDemos-debug.apk...
56+
2.94 MiB / 2.94 MiB [================================] 100.00% 282.47 KiB/s 10s
57+
Download saved to ApiDemos-debug.apk
58+
+ adb -s 0123456789ABCDEF uninstall io.appium.android.apis
59+
+ adb -s 0123456789ABCDEF install ApiDemos-debug.apk
60+
ApiDemos-debug.apk: 1 file pushed. 4.8 MB/s (3084877 bytes in 0.609s)
61+
pkg: /data/local/tmp/ApiDemos-debug.apk
62+
Success
63+
Launch io.appium.android.apis ...
64+
+ adb -s 0123456789ABCDEF shell am start -n io.appium.android.apis/.ApiDemos
65+
```
66+
67+
Run adb command, if multi device connected, `fa` will give you choice to select one.
3268

3369
```
34-
fa install https://example.org/demo.apk
70+
$ fa adb pwd
71+
@ select device
72+
> 3aff8912 Smartion
73+
vv12afvv Google Nexus 5
74+
{selected 3aff8912}
75+
/
3576
```
3677

3778
## Reference

adb.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"net"
8+
"os"
9+
"os/exec"
10+
"strconv"
11+
"strings"
12+
)
13+
14+
const (
15+
_OKAY = "OKAY"
16+
_FAIL = "FAIL"
17+
)
18+
19+
func adbCommand(serial string, args ...string) *exec.Cmd {
20+
fmt.Println("+ adb", "-s", serial, strings.Join(args, " "))
21+
c := exec.Command(adbPath(), args...)
22+
c.Env = append(os.Environ(), "ANDROID_SERIAL="+serial)
23+
return c
24+
}
25+
26+
func panicError(e error) {
27+
if e != nil {
28+
panic(e)
29+
}
30+
}
31+
32+
type AdbConnection struct {
33+
net.Conn
34+
}
35+
36+
// SendPacket data is like "000chost:version"
37+
func (conn *AdbConnection) SendPacket(data string) error {
38+
pktData := fmt.Sprintf("%04x%s", len(data), data)
39+
_, err := conn.Write([]byte(pktData))
40+
return err
41+
}
42+
43+
func (conn *AdbConnection) readN(n int) (v string, err error) {
44+
buf := make([]byte, n)
45+
_, err = io.ReadFull(conn, buf)
46+
if err != nil {
47+
return
48+
}
49+
return string(buf), nil
50+
}
51+
52+
func (conn *AdbConnection) readString() (string, error) {
53+
hexlen, err := conn.readN(4)
54+
if err != nil {
55+
return "", err
56+
}
57+
var length int
58+
_, err = fmt.Sscanf(hexlen, "%04x", &length)
59+
if err != nil {
60+
return "", err
61+
}
62+
return conn.readN(length)
63+
}
64+
65+
// RecvPacket receive data like "OKAY00040028"
66+
func (conn *AdbConnection) RecvPacket() (data string, err error) {
67+
stat, err := conn.readN(4)
68+
if err != nil {
69+
return "", err
70+
}
71+
switch stat {
72+
case _OKAY:
73+
return conn.readString()
74+
case _FAIL:
75+
data, err = conn.readString()
76+
if err != nil {
77+
return
78+
}
79+
err = errors.New(data)
80+
return
81+
default:
82+
return "", fmt.Errorf("Unknown stat: %s", strconv.Quote(stat))
83+
}
84+
}
85+
86+
type AdbClient struct {
87+
Addr string
88+
}
89+
90+
func NewAdbClient() *AdbClient {
91+
return &AdbClient{
92+
Addr: "127.0.0.1:5037",
93+
}
94+
}
95+
96+
var DefaultAdbClient = &AdbClient{
97+
Addr: "127.0.0.1:5037",
98+
}
99+
100+
func (c *AdbClient) newConnection() (conn *AdbConnection, err error) {
101+
netConn, err := net.Dial("tcp", c.Addr)
102+
if err != nil {
103+
return nil, err
104+
}
105+
return &AdbConnection{netConn}, nil
106+
}
107+
108+
// Version returns adb server version
109+
func (c *AdbClient) Version() (string, error) {
110+
conn, err := c.newConnection()
111+
if err != nil {
112+
return "", err
113+
}
114+
if err := conn.SendPacket("host:version"); err != nil {
115+
return "", err
116+
}
117+
return conn.RecvPacket()
118+
}

go.mod

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
module github.com/codeskyblue/fa
22

33
require (
4+
github.com/cavaliercoder/grab v2.0.0+incompatible
45
github.com/manifoldco/promptui v0.3.2
5-
gopkg.in/urfave/cli.v1 v1.20.0
6+
github.com/mattn/go-runewidth v0.0.3 // indirect
7+
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4
8+
github.com/pkg/errors v0.8.0
9+
github.com/shogo82148/androidbinary v0.0.0-20180627093851-01c4bfa8b3b5
10+
github.com/urfave/cli v1.20.0
11+
gopkg.in/cheggaaa/pb.v1 v1.0.25
612
)

install.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"io"
7+
"os"
8+
"regexp"
9+
"strings"
10+
"time"
11+
12+
"github.com/cavaliercoder/grab"
13+
"github.com/pkg/errors"
14+
"github.com/shogo82148/androidbinary/apk"
15+
"github.com/urfave/cli"
16+
pb "gopkg.in/cheggaaa/pb.v1"
17+
)
18+
19+
func httpDownload(dst string, url string) (resp *grab.Response, err error) {
20+
client := grab.NewClient()
21+
req, err := grab.NewRequest(dst, url)
22+
if err != nil {
23+
return nil, err
24+
}
25+
// start download
26+
resp = client.Do(req)
27+
fmt.Printf("Downloading %v...\n", resp.Filename)
28+
29+
// start UI loop
30+
t := time.NewTicker(500 * time.Millisecond)
31+
defer t.Stop()
32+
33+
bar := pb.New(int(resp.Size))
34+
bar.SetMaxWidth(80)
35+
bar.ShowSpeed = true
36+
bar.ShowTimeLeft = false
37+
bar.SetUnits(pb.U_BYTES)
38+
bar.Start()
39+
40+
Loop:
41+
for {
42+
select {
43+
case <-t.C:
44+
bar.Set(int(resp.BytesComplete()))
45+
case <-resp.Done:
46+
bar.Set(int(resp.Size))
47+
bar.Finish()
48+
break Loop
49+
}
50+
}
51+
// check for errors
52+
if err := resp.Err(); err != nil {
53+
return nil, errors.Wrap(err, "download failed")
54+
}
55+
fmt.Println("Download saved to", resp.Filename)
56+
return resp, err
57+
}
58+
59+
func actInstall(ctx *cli.Context) error {
60+
if !ctx.Args().Present() {
61+
return errors.New("apkfile or apkurl should provided")
62+
}
63+
serial, err := chooseOne()
64+
if err != nil {
65+
return err
66+
}
67+
arg := ctx.Args().First()
68+
69+
// download apk
70+
apkpath := arg
71+
if regexp.MustCompile(`^https?://`).MatchString(arg) {
72+
resp, err := httpDownload(".", arg)
73+
if err != nil {
74+
return err
75+
}
76+
apkpath = resp.Filename
77+
}
78+
79+
// parse apk
80+
pkg, err := apk.OpenFile(apkpath)
81+
if err != nil {
82+
return err
83+
}
84+
85+
// handle --force
86+
if ctx.Bool("force") {
87+
pkgName := pkg.PackageName()
88+
adbCommand(serial, "uninstall", pkgName).Run()
89+
}
90+
91+
// install
92+
outBuffer := bytes.NewBuffer(nil)
93+
c := adbCommand(serial, "install", apkpath)
94+
c.Stdout = io.MultiWriter(os.Stdout, outBuffer)
95+
c.Stderr = os.Stderr
96+
if err := c.Run(); err != nil {
97+
return err
98+
}
99+
100+
if strings.Contains(outBuffer.String(), "Failure") {
101+
return errors.New("install failed")
102+
}
103+
if ctx.Bool("launch") {
104+
packageName := pkg.PackageName()
105+
mainActivity, er := pkg.MainActivity()
106+
if er != nil {
107+
fmt.Println("apk have no main-activity")
108+
return nil
109+
}
110+
if !strings.Contains(mainActivity, ".") {
111+
mainActivity = "." + mainActivity
112+
}
113+
fmt.Println("Launch app", packageName, "...")
114+
adbCommand(serial, "shell", "am", "start", "-n", packageName+"/"+mainActivity).Run()
115+
}
116+
return nil
117+
}

0 commit comments

Comments
 (0)