blob: cf0681050a292abfb256af09032bc063a983bd5e [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/ioutil"
"os/user"
"path/filepath"
"time"
"android.googlesource.com/platform/tools/gpu/adb"
"android.googlesource.com/platform/tools/gpu/gapii"
"android.googlesource.com/platform/tools/gpu/log"
"github.com/google/gxui"
"github.com/google/gxui/math"
)
var (
spyport = flag.Int("i", 9286, "gapii TCP port to connect to")
)
type launchNode struct {
text string
items []*launchNode
action *adb.Action
}
func (n *launchNode) String() string {
return n.text
}
func (n *launchNode) Count() int {
return len(n.items)
}
func (n *launchNode) NodeAt(index int) gxui.TreeNode {
return n.items[index]
}
func (n *launchNode) ItemIndex(item gxui.AdapterItem) int {
find, ok := item.(*launchNode)
if !ok {
return -1
}
for i, test := range n.items {
if test == find || test.ItemIndex(item) >= 0 {
return i
}
}
return -1
}
func (n *launchNode) Item() gxui.AdapterItem {
return n
}
func (n *launchNode) Create(theme gxui.Theme) gxui.Control {
label := theme.CreateLabel()
label.SetText(n.text)
return label
}
type launchAdapter struct {
gxui.AdapterBase
launchNode
}
func (a *launchAdapter) Size(t gxui.Theme) math.Size {
return math.Size{W: math.MaxSize.W, H: 18}
}
func CreateLaunchAndroidDialog(theme gxui.Theme, statusLogger log.Logger, 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.CreateTree()
deviceList.OnSelectionChanged(func(sel gxui.AdapterItem) {
device := sel.(*adb.Device)
go func() error {
adapter := &launchAdapter{}
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 {
var pkgNode *launchNode
for _, action := range pkg.Actions {
if action.Name == "android.intent.action.MAIN" {
if pkgNode == nil {
pkgNode = &launchNode{
text: pkg.Name,
}
adapter.items = append(adapter.items, pkgNode)
}
pkgNode.items = append(pkgNode.items, &launchNode{
text: action.Activity,
action: action,
})
}
}
}
driver.CallSync(func() {
packageList.SetAdapter(adapter)
})
return nil
}()
})
packageList.OnDoubleClick(func(gxui.MouseEvent) {
if sel := packageList.Selected(); sel != nil {
if item, ok := sel.(*launchNode); ok {
if item.action != nil {
go func() {
driver.Call(window.Close)
gapii.AdbStart(statusLogger, item.action, adb.TCPPort(*spyport), false)
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...")
load := theme.CreateButton()
load.SetText("Import...")
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)
statusAdapter := CreateLogAdapter(1024, appCtx.Run)
statusLogger := statusAdapter.Logger()
status := theme.CreateList()
status.SetAdapter(statusAdapter)
top.AddChild(status)
bottom := theme.CreateLinearLayout()
bottom.SetDirection(gxui.RightToLeft)
bottom.AddChild(button)
bottom.AddChild(launch)
bottom.AddChild(load)
layout := theme.CreateLinearLayout()
layout.SetDirection(gxui.BottomToTop)
layout.AddChild(bottom)
layout.AddChild(top)
window.AddChild(layout)
var clickSubscription gxui.EventSubscription
capture := func() {
stop := make(chan struct{})
clickSubscription.Unlisten()
theme.Driver().Call(func() {
button.SetText("Stop")
})
clickSubscription = button.OnClick(func(gxui.MouseEvent) {
clickSubscription.Unlisten()
close(stop)
})
go func() {
buf := &bytes.Buffer{}
count, err := gapii.Capture(statusLogger, *spyport, buf, stop)
if count > 0 {
data := buf.Bytes()
log.Infof(statusLogger, "Importing...")
p, err := appCtx.rpc.Import(name.Text(), data)
if err != nil {
panic(err)
}
log.Infof(statusLogger, "Loading...")
appCtx.Run(func() {
appCtx.events.Select(p)
window.Close()
})
} else {
if err != nil {
log.Errorf(statusLogger, "%T %s", err, err.Error())
}
appCtx.Run(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, statusLogger, capture)
})
load.OnClick(func(ev gxui.MouseEvent) {
go func() {
ImportCapture(appCtx, name.Text(), statusLogger)
theme.Driver().Call(func() {
window.Close()
})
}()
})
}
func ImportCapture(appCtx *ApplicationContext, path string, statusLogger log.Logger) {
if path[:2] == "~/" {
usr, _ := user.Current()
path = filepath.Join(usr.HomeDir, path[2:])
}
log.Infof(statusLogger, "Loading %s", path)
data, err := ioutil.ReadFile(path)
if err != nil {
log.Infof(statusLogger, "Failed opening file %s: %s", path, err)
} else if len(data) == 0 {
log.Infof(statusLogger, "Zero size file %s", path)
} else {
log.Infof(statusLogger, "Importing...")
p, err := appCtx.rpc.Import(path, data)
if err != nil {
panic(err)
}
log.Infof(statusLogger, "Loading...")
appCtx.events.Select(p)
}
}