Skip to content

Commit

Permalink
add install and fix screenshot on lower device
Browse files Browse the repository at this point in the history
  • Loading branch information
codeskyblue committed Dec 9, 2018
1 parent 98232d1 commit a9c9b59
Show file tree
Hide file tree
Showing 6 changed files with 367 additions and 58 deletions.
51 changes: 46 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
## Features
- [x] show device selection when multi device connected
- [x] screenshot
- [ ] install support http url
- [ ] support launch after install apk
- [x] install support http url
- [x] support launch after install apk
- [ ] show wlan (ip,mac,signal), enable and disable it

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

## Usage
Screenshot
Show version

```bash
$ fa version
fa version v0.0.5 # just example
```

Screenshot (only png support for now)

```bash
fa screenshot -o screenshot.png
```

~~Install APK~~
Install APK

```
$ fa install ApiDemos-debug.apk
```

Install APK then start app

```
$ fa install --launch ApiDemos-debug.apk
```

Install APK from URL with _uninstall first and launch after installed_


```
$ fa install --force --launch https://github.com/appium/java-client/raw/master/src/test/java/io/appium/java_client/ApiDemos-debug.apk
Downloading ApiDemos-debug.apk...
2.94 MiB / 2.94 MiB [================================] 100.00% 282.47 KiB/s 10s
Download saved to ApiDemos-debug.apk
+ adb -s 0123456789ABCDEF uninstall io.appium.android.apis
+ adb -s 0123456789ABCDEF install ApiDemos-debug.apk
ApiDemos-debug.apk: 1 file pushed. 4.8 MB/s (3084877 bytes in 0.609s)
pkg: /data/local/tmp/ApiDemos-debug.apk
Success
Launch io.appium.android.apis ...
+ adb -s 0123456789ABCDEF shell am start -n io.appium.android.apis/.ApiDemos
```

Run adb command, if multi device connected, `fa` will give you choice to select one.

```
fa install https://example.org/demo.apk
$ fa adb pwd
@ select device
> 3aff8912 Smartion
vv12afvv Google Nexus 5
{selected 3aff8912}
/
```

## Reference
Expand Down
118 changes: 118 additions & 0 deletions adb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package main

import (
"errors"
"fmt"
"io"
"net"
"os"
"os/exec"
"strconv"
"strings"
)

const (
_OKAY = "OKAY"
_FAIL = "FAIL"
)

func adbCommand(serial string, args ...string) *exec.Cmd {
fmt.Println("+ adb", "-s", serial, strings.Join(args, " "))
c := exec.Command(adbPath(), args...)
c.Env = append(os.Environ(), "ANDROID_SERIAL="+serial)
return c
}

func panicError(e error) {
if e != nil {
panic(e)
}
}

type AdbConnection struct {
net.Conn
}

// SendPacket data is like "000chost:version"
func (conn *AdbConnection) SendPacket(data string) error {
pktData := fmt.Sprintf("%04x%s", len(data), data)
_, err := conn.Write([]byte(pktData))
return err
}

func (conn *AdbConnection) readN(n int) (v string, err error) {
buf := make([]byte, n)
_, err = io.ReadFull(conn, buf)
if err != nil {
return
}
return string(buf), nil
}

func (conn *AdbConnection) readString() (string, error) {
hexlen, err := conn.readN(4)
if err != nil {
return "", err
}
var length int
_, err = fmt.Sscanf(hexlen, "%04x", &length)
if err != nil {
return "", err
}
return conn.readN(length)
}

// RecvPacket receive data like "OKAY00040028"
func (conn *AdbConnection) RecvPacket() (data string, err error) {
stat, err := conn.readN(4)
if err != nil {
return "", err
}
switch stat {
case _OKAY:
return conn.readString()
case _FAIL:
data, err = conn.readString()
if err != nil {
return
}
err = errors.New(data)
return
default:
return "", fmt.Errorf("Unknown stat: %s", strconv.Quote(stat))
}
}

type AdbClient struct {
Addr string
}

func NewAdbClient() *AdbClient {
return &AdbClient{
Addr: "127.0.0.1:5037",
}
}

var DefaultAdbClient = &AdbClient{
Addr: "127.0.0.1:5037",
}

func (c *AdbClient) newConnection() (conn *AdbConnection, err error) {
netConn, err := net.Dial("tcp", c.Addr)
if err != nil {
return nil, err
}
return &AdbConnection{netConn}, nil
}

// Version returns adb server version
func (c *AdbClient) Version() (string, error) {
conn, err := c.newConnection()
if err != nil {
return "", err
}
if err := conn.SendPacket("host:version"); err != nil {
return "", err
}
return conn.RecvPacket()
}
8 changes: 7 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
module github.com/codeskyblue/fa

require (
github.com/cavaliercoder/grab v2.0.0+incompatible
github.com/manifoldco/promptui v0.3.2
gopkg.in/urfave/cli.v1 v1.20.0
github.com/mattn/go-runewidth v0.0.3 // indirect
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4
github.com/pkg/errors v0.8.0
github.com/shogo82148/androidbinary v0.0.0-20180627093851-01c4bfa8b3b5
github.com/urfave/cli v1.20.0
gopkg.in/cheggaaa/pb.v1 v1.0.25
)
117 changes: 117 additions & 0 deletions install.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package main

import (
"bytes"
"fmt"
"io"
"os"
"regexp"
"strings"
"time"

"github.com/cavaliercoder/grab"
"github.com/pkg/errors"
"github.com/shogo82148/androidbinary/apk"
"github.com/urfave/cli"
pb "gopkg.in/cheggaaa/pb.v1"
)

func httpDownload(dst string, url string) (resp *grab.Response, err error) {
client := grab.NewClient()
req, err := grab.NewRequest(dst, url)
if err != nil {
return nil, err
}
// start download
resp = client.Do(req)
fmt.Printf("Downloading %v...\n", resp.Filename)

// start UI loop
t := time.NewTicker(500 * time.Millisecond)
defer t.Stop()

bar := pb.New(int(resp.Size))
bar.SetMaxWidth(80)
bar.ShowSpeed = true
bar.ShowTimeLeft = false
bar.SetUnits(pb.U_BYTES)
bar.Start()

Loop:
for {
select {
case <-t.C:
bar.Set(int(resp.BytesComplete()))
case <-resp.Done:
bar.Set(int(resp.Size))
bar.Finish()
break Loop
}
}
// check for errors
if err := resp.Err(); err != nil {
return nil, errors.Wrap(err, "download failed")
}
fmt.Println("Download saved to", resp.Filename)
return resp, err
}

func actInstall(ctx *cli.Context) error {
if !ctx.Args().Present() {
return errors.New("apkfile or apkurl should provided")
}
serial, err := chooseOne()
if err != nil {
return err
}
arg := ctx.Args().First()

// download apk
apkpath := arg
if regexp.MustCompile(`^https?://`).MatchString(arg) {
resp, err := httpDownload(".", arg)
if err != nil {
return err
}
apkpath = resp.Filename
}

// parse apk
pkg, err := apk.OpenFile(apkpath)
if err != nil {
return err
}

// handle --force
if ctx.Bool("force") {
pkgName := pkg.PackageName()
adbCommand(serial, "uninstall", pkgName).Run()
}

// install
outBuffer := bytes.NewBuffer(nil)
c := adbCommand(serial, "install", apkpath)
c.Stdout = io.MultiWriter(os.Stdout, outBuffer)
c.Stderr = os.Stderr
if err := c.Run(); err != nil {
return err
}

if strings.Contains(outBuffer.String(), "Failure") {
return errors.New("install failed")
}
if ctx.Bool("launch") {
packageName := pkg.PackageName()
mainActivity, er := pkg.MainActivity()
if er != nil {
fmt.Println("apk have no main-activity")
return nil
}
if !strings.Contains(mainActivity, ".") {
mainActivity = "." + mainActivity
}
fmt.Println("Launch app", packageName, "...")
adbCommand(serial, "shell", "am", "start", "-n", packageName+"/"+mainActivity).Run()
}
return nil
}
Loading

0 comments on commit a9c9b59

Please sign in to comment.