blob: 1bd7c0823cf8a179b2b4e28ff79a2b5f513bb284 [file]
// Copyright 2018 The Bazel Authors. All rights reserved.
//
// 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 device provides functionality to interact with an android device.
package device
import (
"bufio"
"context"
"errors"
"fmt"
"os"
"strconv"
"strings"
"sync"
"time"
"src/common/golang/pprint"
"src/tools/mi/broker/adb"
)
const (
abiProp = "ro.product.cpu.abi"
abiListProp = "ro.product.cpu.abilist"
buildDescriptionProp = "ro.build.description"
sdkProp = "ro.build.version.sdk"
)
// Controller exposes basic functionality to interact with a device.
type Controller interface {
Install(ctx context.Context, args []string, apks ...string) error
Exec(ctx context.Context, cmd string, args []string, shell bool) (stdOut, stdErr string, err error)
Push(ctx context.Context, from, to string) error
Pull(ctx context.Context, from, to string) error
}
// Device holds information of a particular android device.
type Device struct {
Ctl Controller
abis []string
Props map[string]string
userOnce sync.Once
APILevel int
ABI string
ABIs []string
tmpDir string
user string
}
// New creates and returns a new Device.
func New(ctx context.Context, deviceSerial, port, tmpDir string, adbPath string, useADBRoot bool) (*Device, error) {
ctl, err := initDeviceController(ctx, adbPath, deviceSerial, port, useADBRoot)
if err != nil {
return nil, fmt.Errorf("failed to initialize device controller %v", err)
}
props, err := getProp(ctx, ctl)
if err != nil {
return nil, fmt.Errorf("failed to get device properties. Confirm that your device is visible to `adb devices`, "+
"and make sure to use Pontis for remote connections: %v", err)
}
a, ok := props[sdkProp]
if !ok {
return nil, errors.New("unable to get device API level")
}
apiLevel, err := strconv.Atoi(a)
if err != nil {
return nil, fmt.Errorf("could not parse device API level: %v", err)
}
abi := props[abiProp]
if abi == "" {
return nil, errors.New("unable to get ABIs from device")
}
abis := props[abiListProp]
if abis == "" {
return nil, errors.New("unable to get supported ABIs from device")
}
d := &Device{Ctl: ctl, tmpDir: tmpDir, APILevel: apiLevel, ABI: abi, ABIs: strings.Split(abis, ",")}
return d, nil
}
// Stop stops the application with the provided package name
func (d *Device) Stop(ctx context.Context, manifestPackageName string) {
d.Ctl.Exec(ctx, "am", []string{"force-stop", manifestPackageName}, false)
}
// Launch starts an app with a given activity.
func (d *Device) Launch(ctx context.Context, manifestPackageName, activity string) error {
var err error
var stdOut string
var stdErr string
d.Ctl.Exec(ctx, "am", []string{"force-stop", manifestPackageName}, false)
if activity != "" {
cmp := manifestPackageName + "/" + activity
stdOut, stdErr, err = d.Ctl.Exec(
ctx, "am", []string{"start", "-a", "android.intent.action.MAIN", "-n", cmp}, true)
} else {
stdOut, stdErr, err = d.Ctl.Exec(ctx, "am", []string{"start", manifestPackageName}, true)
if err != nil || strings.Contains(stdOut+stdErr, "Error: ") {
pprint.Warning(
"No or multiple main activities found, falling back to Monkey launcher. Specify the activity you want with `-- --launch_activity` or `-- --nolaunch_app` to launch nothing.")
stdOut, stdErr, err = d.Ctl.Exec(ctx, "monkey", []string{"-p", manifestPackageName, "1"}, true)
}
}
if err == nil && strings.Contains(stdOut+stdErr, "Error: ") {
err = errors.New(stdOut + stdErr)
}
return err
}
// WaitForDebugger sets up the application of the package main to be debuggable.
func (d *Device) WaitForDebugger(ctx context.Context, manifestPackageName string) {
d.Ctl.Exec(ctx, "am", []string{"set-debug-app", "-w", manifestPackageName}, false)
}
// BuildDesc returns a human-readable description of the device build.
func (d *Device) BuildDesc() string {
return d.Props[buildDescriptionProp]
}
func getProp(ctx context.Context, ctl Controller) (map[string]string, error) {
var stdout string
var err error
for i := 0; i < 3; i++ {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
stdout, _, err = ctl.Exec(ctx, "getprop", []string{}, false)
cancel()
if err == nil {
return parseProperties(stdout)
}
}
return nil, err
}
func parseProperties(in string) (map[string]string, error) {
s := bufio.NewScanner(strings.NewReader(in))
props := map[string]string{}
// output looks roughly like:
// [someprop]: [its value]\n
// [otherprop]: [val 2]\n
// [init.svc.vold]: [running]\n
// [anotherprop]: [val 3]\n
for s.Scan() {
l := s.Text()
p := strings.Split(l, ":")
if len(p) != 2 {
continue
}
// p[0] looks like "[prop.name]"
prop := p[0][1 : len(p[0])-1]
var val string
// Multiline value
if strings.HasSuffix(p[1], "[") {
var vb strings.Builder
for s.Scan() {
l = s.Text()
if l == "]" {
val = vb.String()
break
}
vb.WriteString(l)
vb.WriteString("\n")
}
} else {
// Single line value
val = p[1][2 : len(p[1])-1]
}
props[prop] = val
}
return props, nil
}
func initDeviceController(ctx context.Context, adbPath string, deviceSerial, port string, useADBRoot bool) (Controller, error) {
var ctl Controller
var err error
ctl, err = adb.New(ctx, os.Environ(), deviceSerial, port, adbPath, useADBRoot)
if err != nil {
return nil, fmt.Errorf("Unable to connect to device: %v", err)
}
return ctl, nil
}