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
+}