From a4971d2d15dd3ea03c567cb2fe7e43c3c3bb7501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Wed, 13 Apr 2016 11:07:53 +0100 Subject: [PATCH 1/9] Initial commit From mvdan/fdroidcl at 42a1961147a678e2314a41bc2dc2af1fd6c585e1. --- adb/adb.go | 344 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 adb/adb.go diff --git a/adb/adb.go b/adb/adb.go new file mode 100644 index 0000000..aaadb42 --- /dev/null +++ b/adb/adb.go @@ -0,0 +1,344 @@ +// Copyright (c) 2015, Daniel Martí +// See LICENSE for licensing information + +package adb + +import ( + "bufio" + "errors" + "fmt" + "io" + "net" + "os/exec" + "regexp" + "strconv" + "strings" +) + +const port = 5037 + +var ( + // Common install and uninstall errors + ErrInternalError = errors.New("internal error") + ErrUserRestricted = errors.New("user restricted") + ErrAborted = errors.New("aborted") + + // Install errors + ErrAlreadyExists = errors.New("already exists") + ErrInvalidApk = errors.New("invalid apk") + ErrInvalidURI = errors.New("invalid uri") + ErrInsufficientStorage = errors.New("insufficient storage") + ErrDuplicatePackage = errors.New("duplicate package") + ErrNoSharedUser = errors.New("no shared user") + ErrUpdateIncompatible = errors.New("update incompatible") + ErrSharedUserIncompatible = errors.New("shared user incompatible") + ErrMissingSharedLibrary = errors.New("missing shared library") + ErrReplaceCouldntDelete = errors.New("replace couldn't delete") + ErrDexopt = errors.New("dexopt") + ErrOlderSdk = errors.New("older sdk") + ErrConflictingProvider = errors.New("conflicting provider") + ErrNewerSdk = errors.New("newer sdk") + ErrTestOnly = errors.New("test only") + ErrCPUAbiIncompatible = errors.New("cpu abi incompatible") + ErrMissingFeature = errors.New("missing feature") + ErrContainerError = errors.New("combiner error") + ErrInvalidInstallLocation = errors.New("invalid install location") + ErrMediaUnavailable = errors.New("media unavailable") + ErrVerificationTimeout = errors.New("verification timeout") + ErrVerificationFailure = errors.New("verification failure") + ErrPackageChanged = errors.New("package changed") + ErrUIDChanged = errors.New("uid changed") + ErrVersionDowngrade = errors.New("version downgrade") + ErrNotApk = errors.New("not apk") + ErrBadManifest = errors.New("bad manifest") + ErrUnexpectedException = errors.New("unexpected exception") + ErrNoCertificates = errors.New("no certificates") + ErrInconsistentCertificates = errors.New("inconsistent certificates") + ErrCertificateEncoding = errors.New("certificate encoding") + ErrBadPackageName = errors.New("bad package name") + ErrBadSharedUserID = errors.New("bad shared user id") + ErrManifestMalformed = errors.New("manifest malformed") + ErrManifestEmpty = errors.New("manifest empty") + ErrDuplicatePermission = errors.New("duplicate permission") + ErrNoMatchingAbis = errors.New("no matching abis") + + // Uninstall errors + ErrDevicePolicyManager = errors.New("device policy manager") + ErrOwnerBlocked = errors.New("owner blocked") +) + +func parseError(s string) error { + switch s { + case "FAILED_ALREADY_EXISTS": + return ErrAlreadyExists + case "FAILED_INVALID_APK": + return ErrInvalidApk + case "FAILED_INVALID_URI": + return ErrInvalidURI + case "FAILED_INSUFFICIENT_STORAGE": + return ErrInsufficientStorage + case "FAILED_DUPLICATE_PACKAGE": + return ErrDuplicatePackage + case "FAILED_NO_SHARED_USER": + return ErrNoSharedUser + case "FAILED_UPDATE_INCOMPATIBLE": + return ErrUpdateIncompatible + case "FAILED_SHARED_USER_INCOMPATIBLE": + return ErrSharedUserIncompatible + case "FAILED_MISSING_SHARED_LIBRARY": + return ErrMissingSharedLibrary + case "FAILED_REPLACE_COULDNT_DELETE": + return ErrReplaceCouldntDelete + case "FAILED_DEXOPT": + return ErrDexopt + case "FAILED_OLDER_SDK": + return ErrOlderSdk + case "FAILED_CONFLICTING_PROVIDER": + return ErrConflictingProvider + case "FAILED_NEWER_SDK": + return ErrNewerSdk + case "FAILED_TEST_ONLY": + return ErrTestOnly + case "FAILED_CPU_ABI_INCOMPATIBLE": + return ErrCPUAbiIncompatible + case "FAILED_MISSING_FEATURE": + return ErrMissingFeature + case "FAILED_CONTAINER_ERROR": + return ErrContainerError + case "FAILED_INVALID_INSTALL_LOCATION": + return ErrInvalidInstallLocation + case "FAILED_MEDIA_UNAVAILABLE": + return ErrMediaUnavailable + case "FAILED_VERIFICATION_TIMEOUT": + return ErrVerificationTimeout + case "FAILED_VERIFICATION_FAILURE": + return ErrVerificationFailure + case "FAILED_PACKAGE_CHANGED": + return ErrPackageChanged + case "FAILED_UID_CHANGED": + return ErrUIDChanged + case "FAILED_VERSION_DOWNGRADE": + return ErrVersionDowngrade + case "PARSE_FAILED_NOT_APK": + return ErrNotApk + case "PARSE_FAILED_BAD_MANIFEST": + return ErrBadManifest + case "PARSE_FAILED_UNEXPECTED_EXCEPTION": + return ErrUnexpectedException + case "PARSE_FAILED_NO_CERTIFICATES": + return ErrNoCertificates + case "PARSE_FAILED_INCONSISTENT_CERTIFICATES": + return ErrInconsistentCertificates + case "PARSE_FAILED_CERTIFICATE_ENCODING": + return ErrCertificateEncoding + case "PARSE_FAILED_BAD_PACKAGE_NAME": + return ErrBadPackageName + case "PARSE_FAILED_BAD_SHARED_USER_ID": + return ErrBadSharedUserID + case "PARSE_FAILED_MANIFEST_MALFORMED": + return ErrManifestMalformed + case "PARSE_FAILED_MANIFEST_EMPTY": + return ErrManifestEmpty + case "FAILED_INTERNAL_ERROR": + return ErrInternalError + case "FAILED_USER_RESTRICTED": + return ErrUserRestricted + case "FAILED_DUPLICATE_PERMISSION": + return ErrDuplicatePermission + case "FAILED_NO_MATCHING_ABIS": + return ErrNoMatchingAbis + case "FAILED_ABORTED": + return ErrAborted + } + return fmt.Errorf("unknown error: %s", s) +} + +func IsServerRunning() bool { + conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", "127.0.0.1", port)) + if err != nil { + return false + } + conn.Close() + return true +} + +func StartServer() error { + return exec.Command("adb", "start-server").Run() +} + +type Device struct { + ID string + Usb string + Product string + Model string + Device string +} + +var deviceRegex = regexp.MustCompile(`^([^\s]+)\s+device(.*)$`) + +func Devices() ([]*Device, error) { + cmd := exec.Command("adb", "devices", "-l") + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + if err := cmd.Start(); err != nil { + return nil, err + } + var devices []*Device + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + line := scanner.Text() + m := deviceRegex.FindStringSubmatch(line) + if m == nil { + continue + } + device := &Device{ + ID: m[1], + } + extras := m[2] + for _, extra := range strings.Split(extras, " ") { + sp := strings.SplitN(extra, ":", 2) + if len(sp) < 2 { + continue + } + switch sp[0] { + case "usb": + device.Usb = sp[1] + case "product": + device.Product = sp[1] + case "model": + device.Model = sp[1] + case "device": + device.Device = sp[1] + } + } + devices = append(devices, device) + } + return devices, nil +} + +func (d *Device) AdbCmd(args ...string) *exec.Cmd { + cmdArgs := append([]string{"-s", d.ID}, args...) + return exec.Command("adb", cmdArgs...) +} + +func (d *Device) AdbShell(args ...string) *exec.Cmd { + shellArgs := append([]string{"shell"}, args...) + return d.AdbCmd(shellArgs...) +} + +func getFailureCode(r *regexp.Regexp, line string) string { + return r.FindStringSubmatch(line)[1] +} + +var installFailureRegex = regexp.MustCompile(`^Failure \[INSTALL_(.+)\]$`) + +func withOpts(cmd string, opts []string, args ...string) []string { + v := append([]string{"install"}, opts...) + return append(v, args...) +} + +func (d *Device) install(opts []string, path string) error { + cmd := d.AdbCmd(withOpts("install", opts, path)...) + stdout, err := cmd.StdoutPipe() + if err != nil { + return err + } + if err := cmd.Start(); err != nil { + return err + } + line := getResultLine(stdout) + if line == "Success" { + return nil + } + return parseError(getFailureCode(installFailureRegex, line)) +} + +func (d *Device) Install(path string) error { + return d.install(nil, path) +} + +func (d *Device) Upgrade(path string) error { + return d.install([]string{"-r"}, path) +} + +func getResultLine(out io.Reader) string { + scanner := bufio.NewScanner(out) + for scanner.Scan() { + l := scanner.Text() + if strings.HasPrefix(l, "Failure") || strings.HasPrefix(l, "Success") { + return l + } + } + return "" +} + +var deleteFailureRegex = regexp.MustCompile(`^Failure \[DELETE_(.+)\]$`) + +func (d *Device) Uninstall(pkg string) error { + cmd := d.AdbCmd("uninstall", pkg) + stdout, err := cmd.StdoutPipe() + if err != nil { + return err + } + if err := cmd.Start(); err != nil { + return err + } + line := getResultLine(stdout) + if line == "Success" { + return nil + } + return parseError(getFailureCode(deleteFailureRegex, line)) +} + +type Package struct { + ID string + VCode int + VName string +} + +var ( + packageRegex = regexp.MustCompile(`^ Package \[([^\s]+)\]`) + verCodeRegex = regexp.MustCompile(`^ versionCode=([0-9]+)`) + verNameRegex = regexp.MustCompile(`^ versionName=(.+)`) +) + +func (d *Device) Installed() (map[string]Package, error) { + cmd := d.AdbShell("dumpsys", "package", "packages") + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + if err := cmd.Start(); err != nil { + return nil, err + } + packages := make(map[string]Package) + scanner := bufio.NewScanner(stdout) + var cur Package + first := true + for scanner.Scan() { + l := scanner.Text() + if m := packageRegex.FindStringSubmatch(l); m != nil { + if first { + first = false + } else { + packages[cur.ID] = cur + cur = Package{} + } + cur.ID = m[1] + } else if m := verCodeRegex.FindStringSubmatch(l); m != nil { + n, err := strconv.Atoi(m[1]) + if err != nil { + panic(err) + } + cur.VCode = n + } else if m := verNameRegex.FindStringSubmatch(l); m != nil { + cur.VName = m[1] + } + } + if !first { + packages[cur.ID] = cur + } + return packages, nil +} From 94fca801c3cda6a6813f8cf972275cdb988453de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Wed, 13 Apr 2016 11:08:44 +0100 Subject: [PATCH 2/9] Make the host a constant --- adb/adb.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/adb/adb.go b/adb/adb.go index aaadb42..e98a233 100644 --- a/adb/adb.go +++ b/adb/adb.go @@ -15,7 +15,10 @@ import ( "strings" ) -const port = 5037 +const ( + host = "127.0.0.1" + port = 5037 +) var ( // Common install and uninstall errors @@ -154,7 +157,7 @@ func parseError(s string) error { } func IsServerRunning() bool { - conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", "127.0.0.1", port)) + conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", host, port)) if err != nil { return false } From f6fa62afb13411f14321c6408863e2e1a76a4cc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Wed, 13 Apr 2016 11:09:32 +0100 Subject: [PATCH 3/9] Move error stuff to error.go --- adb/adb.go | 137 ------------------------------------------------ adb/error.go | 145 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 137 deletions(-) create mode 100644 adb/error.go diff --git a/adb/adb.go b/adb/adb.go index e98a233..e827160 100644 --- a/adb/adb.go +++ b/adb/adb.go @@ -5,7 +5,6 @@ package adb import ( "bufio" - "errors" "fmt" "io" "net" @@ -20,142 +19,6 @@ const ( port = 5037 ) -var ( - // Common install and uninstall errors - ErrInternalError = errors.New("internal error") - ErrUserRestricted = errors.New("user restricted") - ErrAborted = errors.New("aborted") - - // Install errors - ErrAlreadyExists = errors.New("already exists") - ErrInvalidApk = errors.New("invalid apk") - ErrInvalidURI = errors.New("invalid uri") - ErrInsufficientStorage = errors.New("insufficient storage") - ErrDuplicatePackage = errors.New("duplicate package") - ErrNoSharedUser = errors.New("no shared user") - ErrUpdateIncompatible = errors.New("update incompatible") - ErrSharedUserIncompatible = errors.New("shared user incompatible") - ErrMissingSharedLibrary = errors.New("missing shared library") - ErrReplaceCouldntDelete = errors.New("replace couldn't delete") - ErrDexopt = errors.New("dexopt") - ErrOlderSdk = errors.New("older sdk") - ErrConflictingProvider = errors.New("conflicting provider") - ErrNewerSdk = errors.New("newer sdk") - ErrTestOnly = errors.New("test only") - ErrCPUAbiIncompatible = errors.New("cpu abi incompatible") - ErrMissingFeature = errors.New("missing feature") - ErrContainerError = errors.New("combiner error") - ErrInvalidInstallLocation = errors.New("invalid install location") - ErrMediaUnavailable = errors.New("media unavailable") - ErrVerificationTimeout = errors.New("verification timeout") - ErrVerificationFailure = errors.New("verification failure") - ErrPackageChanged = errors.New("package changed") - ErrUIDChanged = errors.New("uid changed") - ErrVersionDowngrade = errors.New("version downgrade") - ErrNotApk = errors.New("not apk") - ErrBadManifest = errors.New("bad manifest") - ErrUnexpectedException = errors.New("unexpected exception") - ErrNoCertificates = errors.New("no certificates") - ErrInconsistentCertificates = errors.New("inconsistent certificates") - ErrCertificateEncoding = errors.New("certificate encoding") - ErrBadPackageName = errors.New("bad package name") - ErrBadSharedUserID = errors.New("bad shared user id") - ErrManifestMalformed = errors.New("manifest malformed") - ErrManifestEmpty = errors.New("manifest empty") - ErrDuplicatePermission = errors.New("duplicate permission") - ErrNoMatchingAbis = errors.New("no matching abis") - - // Uninstall errors - ErrDevicePolicyManager = errors.New("device policy manager") - ErrOwnerBlocked = errors.New("owner blocked") -) - -func parseError(s string) error { - switch s { - case "FAILED_ALREADY_EXISTS": - return ErrAlreadyExists - case "FAILED_INVALID_APK": - return ErrInvalidApk - case "FAILED_INVALID_URI": - return ErrInvalidURI - case "FAILED_INSUFFICIENT_STORAGE": - return ErrInsufficientStorage - case "FAILED_DUPLICATE_PACKAGE": - return ErrDuplicatePackage - case "FAILED_NO_SHARED_USER": - return ErrNoSharedUser - case "FAILED_UPDATE_INCOMPATIBLE": - return ErrUpdateIncompatible - case "FAILED_SHARED_USER_INCOMPATIBLE": - return ErrSharedUserIncompatible - case "FAILED_MISSING_SHARED_LIBRARY": - return ErrMissingSharedLibrary - case "FAILED_REPLACE_COULDNT_DELETE": - return ErrReplaceCouldntDelete - case "FAILED_DEXOPT": - return ErrDexopt - case "FAILED_OLDER_SDK": - return ErrOlderSdk - case "FAILED_CONFLICTING_PROVIDER": - return ErrConflictingProvider - case "FAILED_NEWER_SDK": - return ErrNewerSdk - case "FAILED_TEST_ONLY": - return ErrTestOnly - case "FAILED_CPU_ABI_INCOMPATIBLE": - return ErrCPUAbiIncompatible - case "FAILED_MISSING_FEATURE": - return ErrMissingFeature - case "FAILED_CONTAINER_ERROR": - return ErrContainerError - case "FAILED_INVALID_INSTALL_LOCATION": - return ErrInvalidInstallLocation - case "FAILED_MEDIA_UNAVAILABLE": - return ErrMediaUnavailable - case "FAILED_VERIFICATION_TIMEOUT": - return ErrVerificationTimeout - case "FAILED_VERIFICATION_FAILURE": - return ErrVerificationFailure - case "FAILED_PACKAGE_CHANGED": - return ErrPackageChanged - case "FAILED_UID_CHANGED": - return ErrUIDChanged - case "FAILED_VERSION_DOWNGRADE": - return ErrVersionDowngrade - case "PARSE_FAILED_NOT_APK": - return ErrNotApk - case "PARSE_FAILED_BAD_MANIFEST": - return ErrBadManifest - case "PARSE_FAILED_UNEXPECTED_EXCEPTION": - return ErrUnexpectedException - case "PARSE_FAILED_NO_CERTIFICATES": - return ErrNoCertificates - case "PARSE_FAILED_INCONSISTENT_CERTIFICATES": - return ErrInconsistentCertificates - case "PARSE_FAILED_CERTIFICATE_ENCODING": - return ErrCertificateEncoding - case "PARSE_FAILED_BAD_PACKAGE_NAME": - return ErrBadPackageName - case "PARSE_FAILED_BAD_SHARED_USER_ID": - return ErrBadSharedUserID - case "PARSE_FAILED_MANIFEST_MALFORMED": - return ErrManifestMalformed - case "PARSE_FAILED_MANIFEST_EMPTY": - return ErrManifestEmpty - case "FAILED_INTERNAL_ERROR": - return ErrInternalError - case "FAILED_USER_RESTRICTED": - return ErrUserRestricted - case "FAILED_DUPLICATE_PERMISSION": - return ErrDuplicatePermission - case "FAILED_NO_MATCHING_ABIS": - return ErrNoMatchingAbis - case "FAILED_ABORTED": - return ErrAborted - } - return fmt.Errorf("unknown error: %s", s) -} - func IsServerRunning() bool { conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", host, port)) if err != nil { diff --git a/adb/error.go b/adb/error.go new file mode 100644 index 0000000..a1e38df --- /dev/null +++ b/adb/error.go @@ -0,0 +1,145 @@ +// Copyright (c) 2015, Daniel Martí +// See LICENSE for licensing information + +package adb + +import ( + "errors" + "fmt" +) + +var ( + // Common install and uninstall errors + ErrInternalError = errors.New("internal error") + ErrUserRestricted = errors.New("user restricted") + ErrAborted = errors.New("aborted") + + // Install errors + ErrAlreadyExists = errors.New("already exists") + ErrInvalidApk = errors.New("invalid apk") + ErrInvalidURI = errors.New("invalid uri") + ErrInsufficientStorage = errors.New("insufficient storage") + ErrDuplicatePackage = errors.New("duplicate package") + ErrNoSharedUser = errors.New("no shared user") + ErrUpdateIncompatible = errors.New("update incompatible") + ErrSharedUserIncompatible = errors.New("shared user incompatible") + ErrMissingSharedLibrary = errors.New("missing shared library") + ErrReplaceCouldntDelete = errors.New("replace couldn't delete") + ErrDexopt = errors.New("dexopt") + ErrOlderSdk = errors.New("older sdk") + ErrConflictingProvider = errors.New("conflicting provider") + ErrNewerSdk = errors.New("newer sdk") + ErrTestOnly = errors.New("test only") + ErrCPUAbiIncompatible = errors.New("cpu abi incompatible") + ErrMissingFeature = errors.New("missing feature") + ErrContainerError = errors.New("combiner error") + ErrInvalidInstallLocation = errors.New("invalid install location") + ErrMediaUnavailable = errors.New("media unavailable") + ErrVerificationTimeout = errors.New("verification timeout") + ErrVerificationFailure = errors.New("verification failure") + ErrPackageChanged = errors.New("package changed") + ErrUIDChanged = errors.New("uid changed") + ErrVersionDowngrade = errors.New("version downgrade") + ErrNotApk = errors.New("not apk") + ErrBadManifest = errors.New("bad manifest") + ErrUnexpectedException = errors.New("unexpected exception") + ErrNoCertificates = errors.New("no certificates") + ErrInconsistentCertificates = errors.New("inconsistent certificates") + ErrCertificateEncoding = errors.New("certificate encoding") + ErrBadPackageName = errors.New("bad package name") + ErrBadSharedUserID = errors.New("bad shared user id") + ErrManifestMalformed = errors.New("manifest malformed") + ErrManifestEmpty = errors.New("manifest empty") + ErrDuplicatePermission = errors.New("duplicate permission") + ErrNoMatchingAbis = errors.New("no matching abis") + + // Uninstall errors + ErrDevicePolicyManager = errors.New("device policy manager") + ErrOwnerBlocked = errors.New("owner blocked") +) + +func parseError(s string) error { + switch s { + case "FAILED_ALREADY_EXISTS": + return ErrAlreadyExists + case "FAILED_INVALID_APK": + return ErrInvalidApk + case "FAILED_INVALID_URI": + return ErrInvalidURI + case "FAILED_INSUFFICIENT_STORAGE": + return ErrInsufficientStorage + case "FAILED_DUPLICATE_PACKAGE": + return ErrDuplicatePackage + case "FAILED_NO_SHARED_USER": + return ErrNoSharedUser + case "FAILED_UPDATE_INCOMPATIBLE": + return ErrUpdateIncompatible + case "FAILED_SHARED_USER_INCOMPATIBLE": + return ErrSharedUserIncompatible + case "FAILED_MISSING_SHARED_LIBRARY": + return ErrMissingSharedLibrary + case "FAILED_REPLACE_COULDNT_DELETE": + return ErrReplaceCouldntDelete + case "FAILED_DEXOPT": + return ErrDexopt + case "FAILED_OLDER_SDK": + return ErrOlderSdk + case "FAILED_CONFLICTING_PROVIDER": + return ErrConflictingProvider + case "FAILED_NEWER_SDK": + return ErrNewerSdk + case "FAILED_TEST_ONLY": + return ErrTestOnly + case "FAILED_CPU_ABI_INCOMPATIBLE": + return ErrCPUAbiIncompatible + case "FAILED_MISSING_FEATURE": + return ErrMissingFeature + case "FAILED_CONTAINER_ERROR": + return ErrContainerError + case "FAILED_INVALID_INSTALL_LOCATION": + return ErrInvalidInstallLocation + case "FAILED_MEDIA_UNAVAILABLE": + return ErrMediaUnavailable + case "FAILED_VERIFICATION_TIMEOUT": + return ErrVerificationTimeout + case "FAILED_VERIFICATION_FAILURE": + return ErrVerificationFailure + case "FAILED_PACKAGE_CHANGED": + return ErrPackageChanged + case "FAILED_UID_CHANGED": + return ErrUIDChanged + case "FAILED_VERSION_DOWNGRADE": + return ErrVersionDowngrade + case "PARSE_FAILED_NOT_APK": + return ErrNotApk + case "PARSE_FAILED_BAD_MANIFEST": + return ErrBadManifest + case "PARSE_FAILED_UNEXPECTED_EXCEPTION": + return ErrUnexpectedException + case "PARSE_FAILED_NO_CERTIFICATES": + return ErrNoCertificates + case "PARSE_FAILED_INCONSISTENT_CERTIFICATES": + return ErrInconsistentCertificates + case "PARSE_FAILED_CERTIFICATE_ENCODING": + return ErrCertificateEncoding + case "PARSE_FAILED_BAD_PACKAGE_NAME": + return ErrBadPackageName + case "PARSE_FAILED_BAD_SHARED_USER_ID": + return ErrBadSharedUserID + case "PARSE_FAILED_MANIFEST_MALFORMED": + return ErrManifestMalformed + case "PARSE_FAILED_MANIFEST_EMPTY": + return ErrManifestEmpty + case "FAILED_INTERNAL_ERROR": + return ErrInternalError + case "FAILED_USER_RESTRICTED": + return ErrUserRestricted + case "FAILED_DUPLICATE_PERMISSION": + return ErrDuplicatePermission + case "FAILED_NO_MATCHING_ABIS": + return ErrNoMatchingAbis + case "FAILED_ABORTED": + return ErrAborted + } + return fmt.Errorf("unknown error: %s", s) +} From 41a332f3612c3b02d3bf1a1c6e76b03ad4825394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Wed, 13 Apr 2016 11:10:43 +0100 Subject: [PATCH 4/9] Split adb.go into server and device --- adb/{adb.go => device.go} | 20 -------------------- adb/server.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 20 deletions(-) rename adb/{adb.go => device.go} (93%) create mode 100644 adb/server.go diff --git a/adb/adb.go b/adb/device.go similarity index 93% rename from adb/adb.go rename to adb/device.go index e827160..ba1807b 100644 --- a/adb/adb.go +++ b/adb/device.go @@ -5,33 +5,13 @@ package adb import ( "bufio" - "fmt" "io" - "net" "os/exec" "regexp" "strconv" "strings" ) -const ( - host = "127.0.0.1" - port = 5037 -) - -func IsServerRunning() bool { - conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", host, port)) - if err != nil { - return false - } - conn.Close() - return true -} - -func StartServer() error { - return exec.Command("adb", "start-server").Run() -} - type Device struct { ID string Usb string diff --git a/adb/server.go b/adb/server.go new file mode 100644 index 0000000..001fe20 --- /dev/null +++ b/adb/server.go @@ -0,0 +1,28 @@ +// Copyright (c) 2015, Daniel Martí +// See LICENSE for licensing information + +package adb + +import ( + "fmt" + "net" + "os/exec" +) + +const ( + host = "127.0.0.1" + port = 5037 +) + +func IsServerRunning() bool { + conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", host, port)) + if err != nil { + return false + } + conn.Close() + return true +} + +func StartServer() error { + return exec.Command("adb", "start-server").Run() +} From 881c3852d744a9e1e3b10cb2305c39bf0f2642a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Wed, 13 Apr 2016 11:25:12 +0100 Subject: [PATCH 5/9] Add first test file --- adb/error_test.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 adb/error_test.go diff --git a/adb/error_test.go b/adb/error_test.go new file mode 100644 index 0000000..407a75d --- /dev/null +++ b/adb/error_test.go @@ -0,0 +1,23 @@ +// Copyright (c) 2015, Daniel Martí +// See LICENSE for licensing information + +package adb + +import "testing" + +func TestParseError(t *testing.T) { + tests := []struct { + in string + want error + }{ + {"FAILED_DEXOPT", ErrDexopt}, + {"PARSE_FAILED_NOT_APK", ErrNotApk}, + {"FAILED_ABORTED", ErrAborted}, + } + for _, c := range tests { + got := parseError(c.in) + if got != c.want { + t.Fatalf("Parse error in %s - wanted %v, got %v", c.in, c.want, got) + } + } +} From cb2482852084183ccd0062a95db65c1ab83c1e3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Wed, 13 Apr 2016 11:28:43 +0100 Subject: [PATCH 6/9] Use a map to identify errors --- adb/error.go | 126 ++++++++++++++++++--------------------------------- 1 file changed, 45 insertions(+), 81 deletions(-) diff --git a/adb/error.go b/adb/error.go index a1e38df..66c3c2d 100644 --- a/adb/error.go +++ b/adb/error.go @@ -58,88 +58,52 @@ var ( ErrOwnerBlocked = errors.New("owner blocked") ) +var errorVals = map[string]error{ + "FAILED_ALREADY_EXISTS": ErrAlreadyExists, + "FAILED_INVALID_APK": ErrInvalidApk, + "FAILED_INVALID_URI": ErrInvalidURI, + "FAILED_INSUFFICIENT_STORAGE": ErrInsufficientStorage, + "FAILED_DUPLICATE_PACKAGE": ErrDuplicatePackage, + "FAILED_NO_SHARED_USER": ErrNoSharedUser, + "FAILED_UPDATE_INCOMPATIBLE": ErrUpdateIncompatible, + "FAILED_SHARED_USER_INCOMPATIBLE": ErrSharedUserIncompatible, + "FAILED_MISSING_SHARED_LIBRARY": ErrMissingSharedLibrary, + "FAILED_REPLACE_COULDNT_DELETE": ErrReplaceCouldntDelete, + "FAILED_DEXOPT": ErrDexopt, + "FAILED_OLDER_SDK": ErrOlderSdk, + "FAILED_CONFLICTING_PROVIDER": ErrConflictingProvider, + "FAILED_NEWER_SDK": ErrNewerSdk, + "FAILED_TEST_ONLY": ErrTestOnly, + "FAILED_CPU_ABI_INCOMPATIBLE": ErrCPUAbiIncompatible, + "FAILED_MISSING_FEATURE": ErrMissingFeature, + "FAILED_CONTAINER_ERROR": ErrContainerError, + "FAILED_INVALID_INSTALL_LOCATION": ErrInvalidInstallLocation, + "FAILED_MEDIA_UNAVAILABLE": ErrMediaUnavailable, + "FAILED_VERIFICATION_TIMEOUT": ErrVerificationTimeout, + "FAILED_VERIFICATION_FAILURE": ErrVerificationFailure, + "FAILED_PACKAGE_CHANGED": ErrPackageChanged, + "FAILED_UID_CHANGED": ErrUIDChanged, + "FAILED_VERSION_DOWNGRADE": ErrVersionDowngrade, + "PARSE_FAILED_NOT_APK": ErrNotApk, + "PARSE_FAILED_BAD_MANIFEST": ErrBadManifest, + "PARSE_FAILED_UNEXPECTED_EXCEPTION": ErrUnexpectedException, + "PARSE_FAILED_NO_CERTIFICATES": ErrNoCertificates, + "PARSE_FAILED_INCONSISTENT_CERTIFICATES": ErrInconsistentCertificates, + "PARSE_FAILED_CERTIFICATE_ENCODING": ErrCertificateEncoding, + "PARSE_FAILED_BAD_PACKAGE_NAME": ErrBadPackageName, + "PARSE_FAILED_BAD_SHARED_USER_ID": ErrBadSharedUserID, + "PARSE_FAILED_MANIFEST_MALFORMED": ErrManifestMalformed, + "PARSE_FAILED_MANIFEST_EMPTY": ErrManifestEmpty, + "FAILED_INTERNAL_ERROR": ErrInternalError, + "FAILED_USER_RESTRICTED": ErrUserRestricted, + "FAILED_DUPLICATE_PERMISSION": ErrDuplicatePermission, + "FAILED_NO_MATCHING_ABIS": ErrNoMatchingAbis, + "FAILED_ABORTED": ErrAborted, +} + func parseError(s string) error { - switch s { - case "FAILED_ALREADY_EXISTS": - return ErrAlreadyExists - case "FAILED_INVALID_APK": - return ErrInvalidApk - case "FAILED_INVALID_URI": - return ErrInvalidURI - case "FAILED_INSUFFICIENT_STORAGE": - return ErrInsufficientStorage - case "FAILED_DUPLICATE_PACKAGE": - return ErrDuplicatePackage - case "FAILED_NO_SHARED_USER": - return ErrNoSharedUser - case "FAILED_UPDATE_INCOMPATIBLE": - return ErrUpdateIncompatible - case "FAILED_SHARED_USER_INCOMPATIBLE": - return ErrSharedUserIncompatible - case "FAILED_MISSING_SHARED_LIBRARY": - return ErrMissingSharedLibrary - case "FAILED_REPLACE_COULDNT_DELETE": - return ErrReplaceCouldntDelete - case "FAILED_DEXOPT": - return ErrDexopt - case "FAILED_OLDER_SDK": - return ErrOlderSdk - case "FAILED_CONFLICTING_PROVIDER": - return ErrConflictingProvider - case "FAILED_NEWER_SDK": - return ErrNewerSdk - case "FAILED_TEST_ONLY": - return ErrTestOnly - case "FAILED_CPU_ABI_INCOMPATIBLE": - return ErrCPUAbiIncompatible - case "FAILED_MISSING_FEATURE": - return ErrMissingFeature - case "FAILED_CONTAINER_ERROR": - return ErrContainerError - case "FAILED_INVALID_INSTALL_LOCATION": - return ErrInvalidInstallLocation - case "FAILED_MEDIA_UNAVAILABLE": - return ErrMediaUnavailable - case "FAILED_VERIFICATION_TIMEOUT": - return ErrVerificationTimeout - case "FAILED_VERIFICATION_FAILURE": - return ErrVerificationFailure - case "FAILED_PACKAGE_CHANGED": - return ErrPackageChanged - case "FAILED_UID_CHANGED": - return ErrUIDChanged - case "FAILED_VERSION_DOWNGRADE": - return ErrVersionDowngrade - case "PARSE_FAILED_NOT_APK": - return ErrNotApk - case "PARSE_FAILED_BAD_MANIFEST": - return ErrBadManifest - case "PARSE_FAILED_UNEXPECTED_EXCEPTION": - return ErrUnexpectedException - case "PARSE_FAILED_NO_CERTIFICATES": - return ErrNoCertificates - case "PARSE_FAILED_INCONSISTENT_CERTIFICATES": - return ErrInconsistentCertificates - case "PARSE_FAILED_CERTIFICATE_ENCODING": - return ErrCertificateEncoding - case "PARSE_FAILED_BAD_PACKAGE_NAME": - return ErrBadPackageName - case "PARSE_FAILED_BAD_SHARED_USER_ID": - return ErrBadSharedUserID - case "PARSE_FAILED_MANIFEST_MALFORMED": - return ErrManifestMalformed - case "PARSE_FAILED_MANIFEST_EMPTY": - return ErrManifestEmpty - case "FAILED_INTERNAL_ERROR": - return ErrInternalError - case "FAILED_USER_RESTRICTED": - return ErrUserRestricted - case "FAILED_DUPLICATE_PERMISSION": - return ErrDuplicatePermission - case "FAILED_NO_MATCHING_ABIS": - return ErrNoMatchingAbis - case "FAILED_ABORTED": - return ErrAborted + if err, e := errorVals[s]; e { + return err } return fmt.Errorf("unknown error: %s", s) } From cf513043a82a79e81d56a4d5a2a3143f4c287953 Mon Sep 17 00:00:00 2001 From: relan Date: Fri, 15 Apr 2016 10:10:12 +0300 Subject: [PATCH 7/9] Request ABIs and API level from devices Library users will be able to implement APK compatibility checking with this information. --- adb/device.go | 69 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/adb/device.go b/adb/device.go index ba1807b..ddc833e 100644 --- a/adb/device.go +++ b/adb/device.go @@ -5,6 +5,7 @@ package adb import ( "bufio" + "errors" "io" "os/exec" "regexp" @@ -13,11 +14,13 @@ import ( ) type Device struct { - ID string - Usb string - Product string - Model string - Device string + ID string + Usb string + Product string + Model string + Device string + ABIs []string + APILevel int } var deviceRegex = regexp.MustCompile(`^([^\s]+)\s+device(.*)$`) @@ -59,6 +62,20 @@ func Devices() ([]*Device, error) { device.Device = sp[1] } } + + props, err := device.AdbProps() + if err != nil { + return nil, err + } + device.ABIs = getAbis(props) + if len(device.ABIs) == 0 { + return nil, errors.New("failed to get device ABIs") + } + device.APILevel, err = strconv.Atoi(props["ro.build.version.sdk"]) + if device.APILevel == 0 { + return nil, errors.New("failed to get device API level") + } + devices = append(devices, device) } return devices, nil @@ -74,10 +91,52 @@ func (d *Device) AdbShell(args ...string) *exec.Cmd { return d.AdbCmd(shellArgs...) } +func (d *Device) AdbProps() (map[string]string, error) { + cmd := d.AdbShell("getprop") + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + if err := cmd.Start(); err != nil { + return nil, err + } + props := make(map[string]string) + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + // [property]: [value] + entry := strings.Split(scanner.Text(), ": ") + if len(entry) == 2 { + key := strings.Trim(entry[0], "[]") + val := strings.Trim(entry[1], "[]") + props[key] = val + } + } + return props, nil +} + func getFailureCode(r *regexp.Regexp, line string) string { return r.FindStringSubmatch(line)[1] } +func getAbis(props map[string]string) []string { + // Android 5.0 and later specify a list of ABIs + abilist, e := props["ro.product.cpu.abilist"] + if e { + return strings.Split(abilist, ",") + } + // Older Android versions specify one primary ABI and optionally + // one secondary ABI + abi, e := props["ro.product.cpu.abi"] + if e { + abi2, e := props["ro.product.cpu.abi2"] + if e { + return []string{abi, abi2} + } + return []string{abi} + } + return nil +} + var installFailureRegex = regexp.MustCompile(`^Failure \[INSTALL_(.+)\]$`) func withOpts(cmd string, opts []string, args ...string) []string { From 4a6306831a3deedd49f2a1bbf4c1285c653e2902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Sun, 24 Apr 2016 18:39:43 +0100 Subject: [PATCH 8/9] device: code simplification and cleanup --- adb/device.go | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/adb/device.go b/adb/device.go index ddc833e..61bc131 100644 --- a/adb/device.go +++ b/adb/device.go @@ -5,7 +5,7 @@ package adb import ( "bufio" - "errors" + "fmt" "io" "os/exec" "regexp" @@ -45,8 +45,7 @@ func Devices() ([]*Device, error) { device := &Device{ ID: m[1], } - extras := m[2] - for _, extra := range strings.Split(extras, " ") { + for _, extra := range strings.Split(m[2], " ") { sp := strings.SplitN(extra, ":", 2) if len(sp) < 2 { continue @@ -69,11 +68,11 @@ func Devices() ([]*Device, error) { } device.ABIs = getAbis(props) if len(device.ABIs) == 0 { - return nil, errors.New("failed to get device ABIs") + return nil, fmt.Errorf("failed to get device ABIs") } device.APILevel, err = strconv.Atoi(props["ro.build.version.sdk"]) if device.APILevel == 0 { - return nil, errors.New("failed to get device API level") + return nil, fmt.Errorf("failed to get device API level") } devices = append(devices, device) @@ -120,21 +119,19 @@ func getFailureCode(r *regexp.Regexp, line string) string { func getAbis(props map[string]string) []string { // Android 5.0 and later specify a list of ABIs - abilist, e := props["ro.product.cpu.abilist"] - if e { + if abilist, e := props["ro.product.cpu.abilist"]; e { return strings.Split(abilist, ",") } // Older Android versions specify one primary ABI and optionally // one secondary ABI abi, e := props["ro.product.cpu.abi"] - if e { - abi2, e := props["ro.product.cpu.abi2"] - if e { - return []string{abi, abi2} - } - return []string{abi} + if !e { + return nil + } + if abi2, e := props["ro.product.cpu.abi2"]; e { + return []string{abi, abi2} } - return nil + return []string{abi} } var installFailureRegex = regexp.MustCompile(`^Failure \[INSTALL_(.+)\]$`) From 2f5d34f72ea1afd0dbbc6d69966b1d04658c7518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Sun, 24 Apr 2016 18:44:34 +0100 Subject: [PATCH 9/9] Make AdbProps() more robust via a regexp The old code is more error-prone (what if there are multiple ": "? what if the trimming doesn't work as expected?) and also contains more logic than necessary. --- adb/device.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/adb/device.go b/adb/device.go index 61bc131..62ab314 100644 --- a/adb/device.go +++ b/adb/device.go @@ -90,6 +90,8 @@ func (d *Device) AdbShell(args ...string) *exec.Cmd { return d.AdbCmd(shellArgs...) } +var propLineRegex = regexp.MustCompile(`^\[(.*)\]: \[(.*)\]$`) + func (d *Device) AdbProps() (map[string]string, error) { cmd := d.AdbShell("getprop") stdout, err := cmd.StdoutPipe() @@ -102,13 +104,12 @@ func (d *Device) AdbProps() (map[string]string, error) { props := make(map[string]string) scanner := bufio.NewScanner(stdout) for scanner.Scan() { - // [property]: [value] - entry := strings.Split(scanner.Text(), ": ") - if len(entry) == 2 { - key := strings.Trim(entry[0], "[]") - val := strings.Trim(entry[1], "[]") - props[key] = val + m := propLineRegex.FindStringSubmatch(scanner.Text()) + if m == nil { + continue } + key, val := m[1], m[2] + props[key] = val } return props, nil }