| // 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) |
| } |