Merge "Ensure we release device in recovery properly"
diff --git a/src/com/android/tradefed/device/DeviceManager.java b/src/com/android/tradefed/device/DeviceManager.java
index 75e8103..7e08c4f 100644
--- a/src/com/android/tradefed/device/DeviceManager.java
+++ b/src/com/android/tradefed/device/DeviceManager.java
@@ -108,7 +108,7 @@
      *
      * <p>serial2 offline
      */
-    private static final String DEVICE_LIST_PATTERN = ".*\n(%s)\\s+(device|offline).*";
+    private static final String DEVICE_LIST_PATTERN = ".*\n(%s)\\s+(device|offline|recovery).*";
 
     private Semaphore mConcurrentFlashLock = null;
 
diff --git a/tests/src/com/android/tradefed/device/DeviceManagerTest.java b/tests/src/com/android/tradefed/device/DeviceManagerTest.java
index 47b0358..3d1d564 100644
--- a/tests/src/com/android/tradefed/device/DeviceManagerTest.java
+++ b/tests/src/com/android/tradefed/device/DeviceManagerTest.java
@@ -1048,6 +1048,67 @@
         assertEquals(1, manager.getDeviceList().size());
     }
 
+    /** Ensure that an unavailable device in recovery mode is released properly. */
+    @Test
+    public void testFreeDevice_recovery() {
+        EasyMock.expect(mMockIDevice.isEmulator()).andStubReturn(Boolean.FALSE);
+        EasyMock.expect(mMockIDevice.getState()).andReturn(DeviceState.ONLINE);
+        EasyMock.expect(mMockStateMonitor.waitForDeviceShell(EasyMock.anyLong()))
+                .andReturn(Boolean.TRUE);
+        mMockStateMonitor.setState(TestDeviceState.NOT_AVAILABLE);
+
+        CommandResult stubAdbDevices = new CommandResult(CommandStatus.SUCCESS);
+        stubAdbDevices.setStdout("List of devices attached\nserial\trecovery\n");
+        EasyMock.expect(
+                        mMockRunUtil.runTimedCmd(
+                                EasyMock.anyLong(), EasyMock.eq("adb"), EasyMock.eq("devices")))
+                .andReturn(stubAdbDevices);
+
+        replayMocks();
+        IManagedTestDevice testDevice = new TestDevice(mMockIDevice, mMockStateMonitor, null);
+        DeviceManager manager = createDeviceManagerNoInit();
+        manager.init(
+                null,
+                null,
+                new ManagedTestDeviceFactory(false, null, null) {
+                    @Override
+                    public IManagedTestDevice createDevice(IDevice idevice) {
+                        mMockTestDevice.setIDevice(idevice);
+                        return testDevice;
+                    }
+
+                    @Override
+                    protected CollectingOutputReceiver createOutputReceiver() {
+                        return new CollectingOutputReceiver() {
+                            @Override
+                            public String getOutput() {
+                                return "/system/bin/pm";
+                            }
+                        };
+                    }
+
+                    @Override
+                    public void setFastbootEnabled(boolean enable) {
+                        // ignore
+                    }
+                });
+
+        mDeviceListener.deviceConnected(mMockIDevice);
+
+        IManagedTestDevice device = (IManagedTestDevice) manager.allocateDevice(mDeviceSelections);
+        assertNotNull(device);
+        // Device becomes unavailable
+        device.setDeviceState(TestDeviceState.NOT_AVAILABLE);
+        // A freed 'unavailable' device becomes UNAVAILABLE state
+        manager.freeDevice(device, FreeDeviceState.UNAVAILABLE);
+        // Ensure device cannot be allocated again
+        ITestDevice device2 = manager.allocateDevice(mDeviceSelections);
+        assertNull(device2);
+        verifyMocks();
+        // We still have the device in the list because device is not lost.
+        assertEquals(1, manager.getDeviceList().size());
+    }
+
     /**
      * Test that when freeing an Unavailable device that is not in 'adb devices' we correctly remove
      * it from our tracking list.