devtools/madb: use the nickname as the console prefix, if applicable.

When the user sets a nickname for a device with 'madb name' command,
the nickname is used as the console output prefix, instead of its
device serial.

Change-Id: Ic48b7adbc1e242d2ea43d2a7cd4ab5efa5431959
diff --git a/exec.go b/exec.go
index 76ce51c..71470c4 100644
--- a/exec.go
+++ b/exec.go
@@ -41,16 +41,16 @@
 		return err
 	}
 
-	devices, err := getDevices()
+	devices, err := getDevices(getDefaultNameFilePath())
 	if err != nil {
 		return err
 	}
 
 	wg := sync.WaitGroup{}
 
-	for _, device := range devices {
+	for _, d := range devices {
 		// capture the current value
-		deviceCopy := device
+		deviceCopy := d
 
 		wg.Add(1)
 		go func() {
@@ -65,15 +65,15 @@
 	return nil
 }
 
-func runMadbExecForDevice(env *cmdline.Env, args []string, device string) error {
+func runMadbExecForDevice(env *cmdline.Env, args []string, d device) error {
 	sh := gosh.NewShell(gosh.Opts{})
 	defer sh.Cleanup()
 
-	cmdArgs := append([]string{"-s", device}, args...)
+	cmdArgs := append([]string{"-s", d.Serial}, args...)
 	cmd := sh.Cmd("adb", cmdArgs...)
 
-	stdout := textutil.PrefixLineWriter(os.Stdout, "["+device+"]\t")
-	stderr := textutil.PrefixLineWriter(os.Stderr, "["+device+"]\t")
+	stdout := textutil.PrefixLineWriter(os.Stdout, "["+d.displayName()+"]\t")
+	stderr := textutil.PrefixLineWriter(os.Stderr, "["+d.displayName()+"]\t")
 	cmd.AddStdoutWriter(stdout)
 	cmd.AddStderrWriter(stderr)
 	cmd.Run()
diff --git a/madb.go b/madb.go
index 0eb7037..085db51 100644
--- a/madb.go
+++ b/madb.go
@@ -9,6 +9,7 @@
 
 import (
 	"fmt"
+	"os"
 	"os/exec"
 	"strings"
 
@@ -43,22 +44,51 @@
 	return nil
 }
 
-// Runs "adb devices" command, and parses the result to get all the device serial numbers.
-func getDevices() ([]string, error) {
+type deviceType string
+
+const (
+	emulator   deviceType = "Emulator"
+	realDevice deviceType = "RealDevice"
+)
+
+type device struct {
+	Serial     string
+	Type       deviceType
+	Qualifiers []string
+	Nickname   string
+}
+
+// Returns the display name which is intended to be used as the console output prefix.
+// This would be the nickname of the device if there is one; otherwise, the serial number is used.
+func (d device) displayName() string {
+	if d.Nickname != "" {
+		return d.Nickname
+	}
+
+	return d.Serial
+}
+
+// Runs "adb devices -l" command, and parses the result to get all the device serial numbers.
+func getDevices(filename string) ([]device, error) {
 	sh := gosh.NewShell(gosh.Opts{})
 	defer sh.Cleanup()
 
 	output := sh.Cmd("adb", "devices", "-l").Stdout()
 
-	return parseDevicesOutput(output)
+	nsm, err := readNicknameSerialMap(filename)
+	if err != nil {
+		fmt.Fprintln(os.Stderr, "Warning: Could not read the nickname file.")
+	}
+
+	return parseDevicesOutput(output, nsm)
 }
 
 // Parses the output generated from "adb devices -l" command and return the list of device serial numbers
 // Devices that are currently offline are excluded from the returned list.
-func parseDevicesOutput(output string) ([]string, error) {
+func parseDevicesOutput(output string, nsm map[string]string) ([]device, error) {
 	lines := strings.Split(output, "\n")
 
-	result := []string{}
+	result := []device{}
 
 	// Check the first line of the output
 	if len(lines) <= 0 || strings.TrimSpace(lines[0]) != "List of devices attached" {
@@ -73,7 +103,37 @@
 			continue
 		}
 
-		result = append(result, strings.Fields(line)[0])
+		// Fill in the device serial and all the qualifiers.
+		d := device{
+			Serial:     fields[0],
+			Qualifiers: fields[2:],
+		}
+
+		// Determine whether this device is an emulator or a real device.
+		if strings.HasPrefix(d.Serial, "emulator") {
+			d.Type = emulator
+		} else {
+			d.Type = realDevice
+		}
+
+		// Determine whether there is a nickname defined for this device,
+		// so that the console output prefix can display the nickname instead of the serial.
+	NSMLoop:
+		for nickname, serial := range nsm {
+			if d.Serial == serial {
+				d.Nickname = nickname
+				break
+			}
+
+			for _, qualifier := range d.Qualifiers {
+				if qualifier == serial {
+					d.Nickname = nickname
+					break NSMLoop
+				}
+			}
+		}
+
+		result = append(result, d)
 	}
 
 	return result, nil
diff --git a/madb_test.go b/madb_test.go
index 6f271f8..2f0567e 100644
--- a/madb_test.go
+++ b/madb_test.go
@@ -14,16 +14,32 @@
 
 	// Normal case
 	output = `List of devices attached
-deviceid01       device usb:3-3.4.3 product:bullhead model:Nexus_5X device:bullhead
-deviceid02       device product:sdk_phone_armv7 model:sdk_phone_armv7 device:generic
+deviceid01          device usb:3-3.4.3 product:bullhead model:Nexus_5X device:bullhead
+emulator-5554       device product:sdk_phone_armv7 model:sdk_phone_armv7 device:generic
 
 `
 
-	result, err := parseDevicesOutput(output)
+	got, err := parseDevicesOutput(output, nil)
 	if err != nil {
 		t.Fatalf("failed to parse the output: %v", err)
 	}
-	if got, want := result, []string{"deviceid01", "deviceid02"}; !reflect.DeepEqual(got, want) {
+
+	want := []device{
+		device{
+			Serial:     "deviceid01",
+			Type:       realDevice,
+			Qualifiers: []string{"usb:3-3.4.3", "product:bullhead", "model:Nexus_5X", "device:bullhead"},
+			Nickname:   "",
+		},
+		device{
+			Serial:     "emulator-5554",
+			Type:       emulator,
+			Qualifiers: []string{"product:sdk_phone_armv7", "model:sdk_phone_armv7", "device:generic"},
+			Nickname:   "",
+		},
+	}
+
+	if !reflect.DeepEqual(got, want) {
 		t.Fatalf("unmatched results: got %v, want %v", got, want)
 	}
 
@@ -32,11 +48,11 @@
 
 `
 
-	result, err = parseDevicesOutput(output)
+	got, err = parseDevicesOutput(output, nil)
 	if err != nil {
 		t.Fatalf("failed to parse the output: %v", err)
 	}
-	if got, want := result, []string{}; !reflect.DeepEqual(got, want) {
+	if want = []device{}; !reflect.DeepEqual(got, want) {
 		t.Fatalf("unmatched results: got %v, want %v", got, want)
 	}
 
@@ -46,11 +62,57 @@
 deviceid02       device product:sdk_phone_armv7 model:sdk_phone_armv7 device:generic
 
 `
-	result, err = parseDevicesOutput(output)
+	got, err = parseDevicesOutput(output, nil)
 	if err != nil {
 		t.Fatalf("failed to parse the output: %v", err)
 	}
-	if got, want := result, []string{"deviceid02"}; !reflect.DeepEqual(got, want) {
+
+	want = []device{
+		device{
+			Serial:     "deviceid02",
+			Type:       realDevice,
+			Qualifiers: []string{"product:sdk_phone_armv7", "model:sdk_phone_armv7", "device:generic"},
+			Nickname:   "",
+		},
+	}
+
+	if !reflect.DeepEqual(got, want) {
+		t.Fatalf("unmatched results: got %v, want %v", got, want)
+	}
+
+	// In case some nicknames are defined.
+	output = `List of devices attached
+deviceid01          device usb:3-3.4.3 product:bullhead model:Nexus_5X device:bullhead
+emulator-5554       device product:sdk_phone_armv7 model:sdk_phone_armv7 device:generic
+
+	`
+
+	nsm := map[string]string{
+		"MyPhone": "deviceid01",
+		"ARMv7":   "model:sdk_phone_armv7",
+	}
+
+	got, err = parseDevicesOutput(output, nsm)
+	if err != nil {
+		t.Fatalf("failed to parse the output: %v", err)
+	}
+
+	want = []device{
+		device{
+			Serial:     "deviceid01",
+			Type:       realDevice,
+			Qualifiers: []string{"usb:3-3.4.3", "product:bullhead", "model:Nexus_5X", "device:bullhead"},
+			Nickname:   "MyPhone",
+		},
+		device{
+			Serial:     "emulator-5554",
+			Type:       emulator,
+			Qualifiers: []string{"product:sdk_phone_armv7", "model:sdk_phone_armv7", "device:generic"},
+			Nickname:   "ARMv7",
+		},
+	}
+
+	if !reflect.DeepEqual(got, want) {
 		t.Fatalf("unmatched results: got %v, want %v", got, want)
 	}
 }