blob: fbc2c3bf1867df97d185b926841a7eebf771ab97 [file] [log] [blame]
// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
"bytes"
"flag"
"fmt"
"io"
"net"
"time"
"android.googlesource.com/platform/tools/gpu/adb"
"github.com/google/gxui"
"github.com/google/gxui/math"
)
var (
spyport = flag.Int("i", 9286, "gapii TCP port to connect to")
)
type launchItem struct {
*adb.Action
}
func (i launchItem) String() string { return fmt.Sprintf("%s/%s", i.Package.Name, i.Activity) }
func CreateLaunchAndroidDialog(theme gxui.Theme, updateStatus func(string, ...interface{}), capture func()) {
driver := theme.Driver()
window := theme.CreateWindow(500, 800, "Launch Android application...")
overlay := theme.CreateBubbleOverlay()
deviceAdapter := gxui.CreateDefaultAdapter()
devices, _ := adb.Devices()
deviceAdapter.SetItems(devices)
deviceList := theme.CreateDropDownList()
deviceList.SetAdapter(deviceAdapter)
deviceList.SetBubbleOverlay(overlay)
packageList := theme.CreateList()
deviceList.OnSelectionChanged(func(sel gxui.AdapterItem) {
device := sel.(*adb.Device)
adapter := gxui.CreateDefaultAdapter()
adapter.SetSize(math.Size{W: math.MaxSize.W, H: 16})
packageList.SetAdapter(adapter)
go func() (err error) {
defer func() {
if err != nil {
driver.Call(func() { adapter.SetItems(err) })
}
}()
items := []launchItem{}
err = device.Root()
switch err {
case nil:
case adb.ErrDeviceNotRooted:
return err
default:
return fmt.Errorf("Failed to restart ADB as root: %v", err)
}
packages, err := device.InstalledPackages()
if len(packages) == 0 || err != nil {
return fmt.Errorf(fmt.Sprintf("Could not get list of installed packages: %v", err))
}
for _, pkg := range packages {
for _, action := range pkg.Actions {
if action.Name == "android.intent.action.MAIN" {
driver.CallSync(func() {
items = append(items, launchItem{action})
adapter.SetItems(items)
})
}
}
}
return nil
}()
})
packageList.OnDoubleClick(func(gxui.MouseEvent) {
if sel := packageList.Selected(); sel != nil {
if item, ok := sel.(launchItem); ok {
go func() {
driver.Call(window.Close)
updateStatus("Disabling SELinux enforcing...")
item.Package.Device.SetSELinuxEnforcing(false)
updateStatus("Setting LD_PRELOAD...")
item.Package.SetWrapProperties("LD_PRELOAD=/data/spy.so")
updateStatus("Forwarding port...")
item.Package.Device.Forward(adb.TCPPort(*spyport), adb.NamedAbstractSocket("gfxspy"))
updateStatus("Starting activity...")
item.Package.Device.StartActivity(*item.Action)
capture()
}()
}
}
})
layout := theme.CreateLinearLayout()
layout.AddChild(deviceList)
layout.AddChild(packageList)
window.AddChild(layout)
window.AddChild(overlay)
if len(devices) > 0 {
deviceList.Select(devices[0])
}
}
func CreateTakeCaptureDialog(appCtx *ApplicationContext) {
theme := appCtx.Theme()
window := theme.CreateWindow(500, 200, "Take capture")
launch := theme.CreateButton()
launch.SetText("Launch...")
button := theme.CreateButton()
button.SetText("Capture...")
namelbl := theme.CreateLabel()
namelbl.SetText("Capture name:")
name := theme.CreateTextBox()
name.SetDesiredWidth(math.MaxSize.W)
name.SetText(time.Now().String())
top := theme.CreateLinearLayout()
top.SetSizeMode(gxui.Fill)
top.SetDirection(gxui.TopToBottom)
row := theme.CreateLinearLayout()
row.SetDirection(gxui.LeftToRight)
row.AddChild(namelbl)
row.AddChild(name)
top.AddChild(row)
status := theme.CreateLabel()
top.AddChild(status)
updateStatus := func(s string, args ...interface{}) {
theme.Driver().Call(func() {
status.SetText(fmt.Sprintf(s, args...))
})
}
bottom := theme.CreateLinearLayout()
bottom.SetDirection(gxui.RightToLeft)
bottom.AddChild(button)
bottom.AddChild(launch)
layout := theme.CreateLinearLayout()
layout.SetDirection(gxui.BottomToTop)
layout.AddChild(bottom)
layout.AddChild(top)
window.AddChild(layout)
var clickSubscription gxui.EventSubscription
capture := func() {
stop := make(signal)
clickSubscription.Unlisten()
button.SetText("Stop")
clickSubscription = button.OnClick(func(gxui.MouseEvent) {
clickSubscription.Unlisten()
stop.raise()
})
go func() {
if data := takeCapture(appCtx, stop, updateStatus); data != nil {
updateStatus("Importing...")
id, err := appCtx.Rpc().Import(appCtx.Logger(), name.Text(), data)
if err != nil {
panic(err)
}
updateStatus("Loading...")
appCtx.LoadCapture(id, true)
theme.Driver().Call(func() {
window.Close()
})
} else {
theme.Driver().Call(func() {
button.SetText("Close")
button.OnClick(func(gxui.MouseEvent) {
window.Close()
})
})
}
}()
}
clickSubscription = button.OnClick(func(gxui.MouseEvent) { capture() })
launch.OnClick(func(ev gxui.MouseEvent) {
CreateLaunchAndroidDialog(theme, updateStatus, capture)
})
}
type signal chan struct{}
func (s signal) raise() {
close(s)
}
func (s signal) signaled() bool {
select {
case <-s:
return true
default:
return false
}
}
type tcUpdate struct {
msg string
data []byte
}
func takeCapture(appCtx *ApplicationContext, stop signal, updateStatus func(string, ...interface{})) []byte {
var conn net.Conn
var err error
updateStatus("Waiting for connection to localhost:%d...", *spyport)
waiting:
for {
if stop.signaled() {
return nil
}
time.Sleep(500 * time.Millisecond)
conn, err = net.Dial("tcp", fmt.Sprintf("localhost:%d", *spyport))
if err == nil {
buf := &bytes.Buffer{}
bytesWritten := int64(0)
for {
if stop.signaled() {
conn.Close()
return buf.Bytes()
}
conn.SetReadDeadline(time.Now().Add(time.Millisecond * 100)) // Allow for stop event and UI refreshes.
n, err := io.CopyN(buf, conn, 1024*64)
if err, neterr := err.(net.Error); neterr {
if err.Temporary() || err.Timeout() {
bytesWritten += n
updateStatus("Capturing...\n%v bytes", bytesWritten)
continue
}
}
switch err {
case nil:
bytesWritten += n
updateStatus("Capturing...\n%v bytes", bytesWritten)
case io.EOF:
if len(buf.Bytes()) == 0 {
// ADB has an annoying tendancy to insta-close forwarded sockets when
// there's no application waiting for the connection. Treat this as
// another waiting-for-connection case.
continue waiting
}
updateStatus("Done")
return buf.Bytes()
default:
updateStatus("Connection error: %v", err)
return buf.Bytes()
}
}
}
}
return nil
}