devtools/madb: add "madb start" command.
Adds the basic "madb start" command that launches the specified app on
all devices.
Change-Id: Id9708195f824f7c006b04b365f17ed60e0defdcb
diff --git a/doc.go b/doc.go
index 3ca1637..6ba1a00 100644
--- a/doc.go
+++ b/doc.go
@@ -17,6 +17,7 @@
The madb commands are:
exec Run the provided adb command on all devices and emulators
concurrently
+ start Launch your app on all devices
name Manage device nicknames
help Display help for commands or topics
@@ -63,6 +64,31 @@
Comma-separated device serials, qualifiers, or nicknames (set by 'madb
name'). Command will be run only on specified devices.
+Madb start - Launch your app on all devices
+
+Launches your app on all devices.
+
+Usage:
+ madb start [flags] <application_id> <activity_name>
+
+<application_id> is usually the package name where the activities are defined.
+(See:
+http://tools.android.com/tech-docs/new-build-system/applicationid-vs-packagename)
+
+<activity_name> is the Java class name for the activity you want to launch. If
+the package name of the activity is different from the application ID, the
+activity name must be a fully-qualified name (e.g.,
+com.yourcompany.yourapp.MainActivity).
+
+The madb start flags are:
+ -d=false
+ Restrict the command to only run on real devices.
+ -e=false
+ Restrict the command to only run on emulators.
+ -n=
+ Comma-separated device serials, qualifiers, or nicknames (set by 'madb
+ name'). Command will be run only on specified devices.
+
Madb name - Manage device nicknames
Manages device nicknames, which are meant to be more human-friendly compared to
diff --git a/exec.go b/exec.go
index 7b5307f..e8afec8 100644
--- a/exec.go
+++ b/exec.go
@@ -6,7 +6,6 @@
import (
"os"
- "sync"
"v.io/x/lib/cmdline"
"v.io/x/lib/gosh"
@@ -14,7 +13,7 @@
)
var cmdMadbExec = &cmdline.Command{
- Runner: cmdline.RunnerFunc(runMadbExec),
+ Runner: subCommandRunner{runMadbExecForDevice},
Name: "exec",
Short: "Run the provided adb command on all devices and emulators concurrently",
Long: `
@@ -34,37 +33,6 @@
`,
}
-func runMadbExec(env *cmdline.Env, args []string) error {
- // TODO(youngseokyoon): consider making this function generic
-
- if err := startAdbServer(); err != nil {
- return err
- }
-
- devices, err := getSpecifiedDevices()
- if err != nil {
- return err
- }
-
- wg := sync.WaitGroup{}
-
- for _, d := range devices {
- // capture the current value
- deviceCopy := d
-
- wg.Add(1)
- go func() {
- // TODO(youngseokyoon): handle the error returned from here.
- runMadbExecForDevice(env, args, deviceCopy)
- wg.Done()
- }()
- }
-
- wg.Wait()
-
- return nil
-}
-
func runMadbExecForDevice(env *cmdline.Env, args []string, d device) error {
sh := gosh.NewShell(gosh.Opts{})
defer sh.Cleanup()
diff --git a/madb.go b/madb.go
index 2f7efb2..45c7c40 100644
--- a/madb.go
+++ b/madb.go
@@ -8,10 +8,12 @@
package main
import (
+ "bytes"
"fmt"
"os"
"os/exec"
"strings"
+ "sync"
"v.io/x/lib/cmdline"
"v.io/x/lib/gosh"
@@ -30,7 +32,7 @@
}
var cmdMadb = &cmdline.Command{
- Children: []*cmdline.Command{cmdMadbExec, cmdMadbName},
+ Children: []*cmdline.Command{cmdMadbExec, cmdMadbStart, cmdMadbName},
Name: "madb",
Short: "Multi-device Android Debug Bridge",
Long: `
@@ -220,3 +222,57 @@
return false
}
+
+type subCommandFunc func(env *cmdline.Env, args []string, d device) error
+
+type subCommandRunner struct {
+ subCmd subCommandFunc
+}
+
+var _ cmdline.Runner = (*subCommandRunner)(nil)
+
+// Invokes the sub command on all the devices in parallel.
+func (r subCommandRunner) Run(env *cmdline.Env, args []string) error {
+ if err := startAdbServer(); err != nil {
+ return err
+ }
+
+ devices, err := getSpecifiedDevices()
+ if err != nil {
+ return err
+ }
+
+ wg := sync.WaitGroup{}
+
+ var errs []error
+ var errDevices []device
+
+ for _, d := range devices {
+ // Capture the current value.
+ deviceCopy := d
+
+ wg.Add(1)
+ go func() {
+ // Remember the first error returned by the sub command.
+ if e := r.subCmd(env, args, deviceCopy); err == nil && e != nil {
+ errs = append(errs, e)
+ errDevices = append(errDevices, deviceCopy)
+ }
+ wg.Done()
+ }()
+ }
+
+ wg.Wait()
+
+ // Report any errors returned from the go-routines.
+ if errs != nil {
+ buffer := bytes.Buffer{}
+ buffer.WriteString("Error occurred while running the command on the following devices:")
+ for i := 0; i < len(errs); i++ {
+ buffer.WriteString("\n[" + errDevices[i].displayName() + "]\t" + errs[i].Error())
+ }
+ return fmt.Errorf(buffer.String())
+ }
+
+ return nil
+}
diff --git a/start.go b/start.go
new file mode 100644
index 0000000..5287715
--- /dev/null
+++ b/start.go
@@ -0,0 +1,67 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "fmt"
+ "os"
+ "strings"
+
+ "v.io/x/lib/cmdline"
+ "v.io/x/lib/gosh"
+ "v.io/x/lib/textutil"
+)
+
+var cmdMadbStart = &cmdline.Command{
+ Runner: subCommandRunner{runMadbStartForDevice},
+ Name: "start",
+ Short: "Launch your app on all devices",
+ Long: `
+Launches your app on all devices.
+
+`,
+ ArgsName: "<application_id> <activity_name>",
+ ArgsLong: `
+<application_id> is usually the package name where the activities are defined.
+(See: http://tools.android.com/tech-docs/new-build-system/applicationid-vs-packagename)
+
+<activity_name> is the Java class name for the activity you want to launch.
+If the package name of the activity is different from the application ID, the activity name must be a fully-qualified name (e.g., com.yourcompany.yourapp.MainActivity).
+`,
+}
+
+func runMadbStartForDevice(env *cmdline.Env, args []string, d device) error {
+ sh := gosh.NewShell(gosh.Opts{})
+ defer sh.Cleanup()
+
+ if len(args) != 2 {
+ // TODO(youngseokyoon): Extract the application ID and activity name from the build scripts in the current directory.
+ return fmt.Errorf("You must provide the application ID and the activity name.")
+ }
+
+ appID, activity := args[0], args[1]
+
+ // In case the activity name is a simple name (i.e. without the package name), add a dot in the front.
+ // This is a shorthand syntax to prepend the activity name with the package name provided in the manifest.
+ // http://developer.android.com/guide/topics/manifest/activity-element.html#nm
+ if !strings.ContainsAny(activity, ".") {
+ activity = "." + activity
+ }
+
+ // TODO(youngseokyoon): add a flag for not stopping the activity when it is currently running.
+ // More details on the "adb shell am" command can be found at: http://developer.android.com/tools/help/shell.html#am
+ cmdArgs := []string{"-s", d.Serial, "shell", "am", "start", "-S", "-n", appID + "/" + activity}
+ cmd := sh.Cmd("adb", cmdArgs...)
+
+ stdout := textutil.PrefixLineWriter(os.Stdout, "["+d.displayName()+"]\t")
+ stderr := textutil.PrefixLineWriter(os.Stderr, "["+d.displayName()+"]\t")
+ cmd.AddStdoutWriter(stdout)
+ cmd.AddStderrWriter(stderr)
+ cmd.Run()
+ stdout.Flush()
+ stderr.Flush()
+
+ return sh.Err
+}