From 49dbce382b31b0bd14834393a15a21acb8e56102 Mon Sep 17 00:00:00 2001
From: Zach Klippenstein <zach.klippenstein@gmail.com>
Date: Sat, 21 May 2016 23:23:26 -0700
Subject: [PATCH] Make Device.State return DeviceState instead of string.

---
 device.go             |  6 +++---
 device_state.go       | 32 ++++++++++++++++++++++++++++++++
 device_state_test.go  | 31 +++++++++++++++++++++++++++++++
 device_watcher.go     | 29 ++++-------------------------
 devicestate_string.go |  4 ++--
 5 files changed, 72 insertions(+), 30 deletions(-)
 create mode 100644 device_state.go
 create mode 100644 device_state_test.go

diff --git a/device.go b/device.go
index 6a6b2a1..594fcf9 100644
--- a/device.go
+++ b/device.go
@@ -46,10 +46,10 @@ func (c *Device) DevicePath() (string, error) {
 	return attr, wrapClientError(err, c, "DevicePath")
 }
 
-// State returns the connection state of the device (e.g. "device").
-func (c *Device) State() (string, error) {
+func (c *Device) State() (DeviceState, error) {
 	attr, err := c.getAttribute("get-state")
-	return attr, wrapClientError(err, c, "State")
+	state, err := parseDeviceState(attr)
+	return state, wrapClientError(err, c, "State")
 }
 
 func (c *Device) DeviceInfo() (*DeviceInfo, error) {
diff --git a/device_state.go b/device_state.go
new file mode 100644
index 0000000..17cb0ed
--- /dev/null
+++ b/device_state.go
@@ -0,0 +1,32 @@
+package adb
+
+import "github.com/zach-klippenstein/goadb/util"
+
+// DeviceState represents one of the 3 possible states adb will report devices.
+// A device can be communicated with when it's in StateOnline.
+// A USB device will make the following state transitions:
+// 	Plugged in: StateDisconnected->StateOffline->StateOnline
+// 	Unplugged:  StateOnline->StateDisconnected
+//go:generate stringer -type=DeviceState
+type DeviceState int8
+
+const (
+	StateInvalid DeviceState = iota
+	StateDisconnected
+	StateOffline
+	StateOnline
+)
+
+var deviceStateStrings = map[string]DeviceState{
+	"":        StateDisconnected,
+	"offline": StateOffline,
+	"device":  StateOnline,
+}
+
+func parseDeviceState(str string) (DeviceState, error) {
+	state, ok := deviceStateStrings[str]
+	if !ok {
+		return StateInvalid, util.Errorf(util.ParseError, "invalid device state: %q", state)
+	}
+	return state, nil
+}
diff --git a/device_state_test.go b/device_state_test.go
new file mode 100644
index 0000000..2ab10dc
--- /dev/null
+++ b/device_state_test.go
@@ -0,0 +1,31 @@
+package adb
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestParseDeviceState(t *testing.T) {
+	for _, test := range []struct {
+		String    string
+		WantState DeviceState
+		WantName  string
+		WantError error // Compared by Error() message.
+	}{
+		{"", StateDisconnected, "StateDisconnected", nil},
+		{"offline", StateOffline, "StateOffline", nil},
+		{"device", StateOnline, "StateOnline", nil},
+		{"bad", StateInvalid, "StateInvalid", errors.New(`ParseError: invalid device state: "StateInvalid"`)},
+	} {
+		state, err := parseDeviceState(test.String)
+		if test.WantError == nil {
+			assert.NoError(t, err)
+		} else {
+			assert.EqualError(t, err, test.WantError.Error())
+		}
+		assert.Equal(t, test.WantState, state)
+		assert.Equal(t, test.WantName, state.String())
+	}
+}
diff --git a/device_watcher.go b/device_watcher.go
index fe1866e..3ae4fc4 100644
--- a/device_watcher.go
+++ b/device_watcher.go
@@ -21,6 +21,8 @@ type DeviceWatcher struct {
 }
 
 // DeviceStateChangedEvent represents a device state transition.
+// Contains the device’s old and new states, but also provides methods to query the
+// type of state transition.
 type DeviceStateChangedEvent struct {
 	Serial   string
 	OldState DeviceState
@@ -37,27 +39,6 @@ func (s DeviceStateChangedEvent) WentOffline() bool {
 	return s.OldState == StateOnline && s.NewState != StateOnline
 }
 
-// DeviceState represents one of the 3 possible states adb will report devices.
-// A device can be communicated with when it's in StateOnline.
-// A USB device will transition from StateDisconnected->StateOffline->StateOnline when
-// plugged in, and then StateOnline->StateDisconnected when unplugged.
-// If code doesn't care about specific states, DeviceStateChangedEvent provides methods
-// to query at a higher level.
-//go:generate stringer -type=DeviceState
-type DeviceState int8
-
-const (
-	StateDisconnected DeviceState = iota
-	StateOffline
-	StateOnline
-)
-
-var deviceStateStrings = map[string]DeviceState{
-	"":        StateDisconnected,
-	"offline": StateOffline,
-	"device":  StateOnline,
-}
-
 type deviceWatcherImpl struct {
 	server server
 
@@ -218,10 +199,8 @@ func parseDeviceStates(msg string) (states map[string]DeviceState, err error) {
 		}
 
 		serial, stateString := fields[0], fields[1]
-		state, ok := deviceStateStrings[stateString]
-		if !ok {
-			err = util.Errorf(util.ParseError, "invalid device state: %s", state)
-		}
+		var state DeviceState
+		state, err = parseDeviceState(stateString)
 		states[serial] = state
 	}
 
diff --git a/devicestate_string.go b/devicestate_string.go
index 77ac037..2cbe67d 100644
--- a/devicestate_string.go
+++ b/devicestate_string.go
@@ -4,9 +4,9 @@ package adb
 
 import "fmt"
 
-const _DeviceState_name = "StateDisconnectedStateOfflineStateOnline"
+const _DeviceState_name = "StateInvalidStateDisconnectedStateOfflineStateOnline"
 
-var _DeviceState_index = [...]uint8{0, 17, 29, 40}
+var _DeviceState_index = [...]uint8{0, 12, 29, 41, 52}
 
 func (i DeviceState) String() string {
 	if i < 0 || i >= DeviceState(len(_DeviceState_index)-1) {