blob: fb9be26d8e7d57c968bd5ff71b6d311d474d57f0 [file] [log] [blame]
// Copyright (C) 2016 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 gapir
import (
"fmt"
"io"
"time"
"android.googlesource.com/platform/tools/gpu/client/adb"
"android.googlesource.com/platform/tools/gpu/client/android"
"android.googlesource.com/platform/tools/gpu/framework/app"
"android.googlesource.com/platform/tools/gpu/framework/device"
"android.googlesource.com/platform/tools/gpu/framework/id"
"android.googlesource.com/platform/tools/gpu/framework/log"
"android.googlesource.com/platform/tools/gpu/framework/task"
"android.googlesource.com/platform/tools/gpu/gapid/auth"
"android.googlesource.com/platform/tools/gpu/gapid/pkgdata"
)
const (
androidPort = adb.NamedAbstractSocket("gapir")
gapirPackageName = "com.google.android.gapir"
installGapirAttempts = 5
)
var (
// ErrMultipleGapirPackages is the error returned when multiple GAPIR
// packages are installed on the local Android device.
ErrMultipleGapirPackages = fmt.Errorf("Multiple %v packages found", gapirPackageName)
// ErrCannotInstallGapir is the error returned when the GAPIR APK cannot be
// be installed on the local Android device.
ErrCannotInstallGapir = fmt.Errorf("Cannot install GAPIR after %v attempts", installGapirAttempts)
)
// NewLocalAndroidDevice returns a Device representing the local Android device
// with the given serial.
func NewLocalAndroidDevice(ctx log.Context, serial string) (Device, error) {
ctx = ctx.S("serial", serial)
d, err := findLocalAndroidDevice(ctx, serial)
if err != nil {
return nil, err
}
return newLAD(d), nil
}
func findLocalAndroidDevice(ctx log.Context, serial string) (adb.Device, error) {
l, err := adb.Devices(ctx)
if err != nil {
return nil, err
}
for _, d := range l {
if d.Info().Serial == serial {
return d, nil
}
}
return nil, ctx.AsError("Android device not found")
}
// maybeInstallGapir attempts to install the GAPIR APK with the specified ABI
// on the target device. If abi is nil, then the preferred device ABI is used.
func maybeInstallGapir(ctx log.Context, d adb.Device, reqABI *device.ABI) (*android.InstalledPackage, error) {
var abi device.ABI
if reqABI != nil {
abi = *reqABI
supported := false
// Check the device actually supports the requested ABI.
for _, t := range d.Info().Configuration.ABIs {
if t == abi {
supported = true
break
}
}
if !supported {
return nil, ctx.V("abi", *reqABI).AsError("Device does not support requested ABI.")
}
} else {
abi = d.ABI()
}
ctx = ctx.V("abi", abi)
for i := 0; i < installGapirAttempts; i++ {
pkgs, err := d.InstalledPackages(ctx)
if err != nil {
return nil, err
}
pkgs = pkgs.FindByName(gapirPackageName)
switch len(pkgs) {
case 0:
// Install GAPIR on the device.
path, err := pkgdata.File(ctx, abi, "gapir.apk")
if err != nil {
return nil, err
}
ctx.Info().S("apk", path.String()).Log("Installing GAPIR...")
if err := d.InstallAPK(ctx, path.System(), true, false); err != nil {
return nil, err
}
case 1:
if reqABI == nil {
ctx.Info().Log("Found installed GAPIR. Not reinstalling.")
return pkgs[0], nil
}
if pkgs[0].ABI == abi {
ctx.Info().Log("Installed GAPIR has compatible ABI. Not reinstalling.")
return pkgs[0], nil
}
// Not the ABI we are after. Uninstall it.
ctx.Info().Log("Installed GAPIR has incompatible ABI. Uninstalling.")
if err := pkgs[0].Uninstall(ctx); err != nil {
return nil, err
}
default:
return nil, ErrMultipleGapirPackages
}
}
return nil, ErrCannotInstallGapir
}
// lad is a wrapper around deviceBase for Local Android Devices.
// It lazily launches the GAPIR APK on the device when
type lad struct {
id id.ID
adb adb.Device
dev *deviceBase
unforward func()
}
func newLAD(d adb.Device) *lad {
return &lad{
id: id.OfString("local android device: " + d.Info().Serial),
adb: d,
}
}
func (d *lad) launch(ctx log.Context, abi *device.ABI) error {
if d.dev != nil {
info, err := d.dev.Info(ctx)
if err != nil {
return err
}
if abi == nil {
return nil // We don't care which ABI GAPIR is launched with.
}
if len(info.Configuration.ABIs) > 0 {
if info.Configuration.ABIs[0] == *abi {
return nil // Already installed will the specified ABI.
}
ctx.Info().
V("desired", *abi).
V("supported", info.Configuration.ABIs).
Log("Relaunching GAPIR on device as it doesn't support desired ABI.")
}
}
// Install GAPIR
pkg, err := maybeInstallGapir(ctx, d.adb, abi)
if err != nil {
return err
}
// Set up the port forward
if d.unforward != nil {
d.unforward()
}
localPort, err := adb.LocalFreeTCPPort()
if err != nil {
return err
}
if err := d.adb.Forward(ctx, localPort, androidPort); err != nil {
return err
}
// Remove the port forward when the context is closed.
unforwardCtx, unforward := task.WithCancel(ctx)
unforwardSignal := task.ShouldStop(unforwardCtx)
go func() {
<-unforwardSignal
d.adb.RemoveForward(ctx, localPort)
}()
app.AddCleanupSignal(unforwardSignal)
d.unforward = unforward
// Launch GAPIR
if err := d.adb.StartActivity(ctx, *pkg.Actions[0]); err != nil {
return err
}
d.dev = &deviceBase{
port: int(localPort),
authToken: auth.NoAuth,
device: *d.adb.Info(),
}
// Wait for connection to become stable
for i := 0; i < 10; i++ {
if _, err := d.dev.ping(ctx); err == nil {
break
}
time.Sleep(time.Second)
}
return nil
}
func (d *lad) ID() id.ID {
return d.id
}
func (d *lad) Info(ctx log.Context) (*device.Information, error) {
if err := d.launch(ctx, nil); err != nil {
return nil, err
}
return d.dev.Info(ctx)
}
func (d *lad) Connect(ctx log.Context, abi *device.ABI) (io.ReadWriteCloser, error) {
if err := d.launch(ctx, abi); err != nil {
return nil, err
}
return d.dev.Connect(ctx, abi)
}