Merge "Update proto to have a field for error type"
diff --git a/atest/atest.py b/atest/atest.py
index 8611e21..c4b993e 100755
--- a/atest/atest.py
+++ b/atest/atest.py
@@ -563,17 +563,17 @@
 # pylint: disable=too-many-statements
 # pylint: disable=too-many-branches
 # pylint: disable=too-many-return-statements
-def main(argv, results_dir):
+def main(argv, results_dir, args):
     """Entry point of atest script.
 
     Args:
         argv: A list of arguments.
         results_dir: A directory which stores the ATest execution information.
+        args: An argspace.Namespace class instance holding parsed args.
 
     Returns:
         Exit code.
     """
-    args = _parse_args(argv)
     _configure_logging(args.verbose)
     _validate_args(args)
     metrics_utils.get_start_time()
@@ -707,14 +707,16 @@
 
 if __name__ == '__main__':
     RESULTS_DIR = make_test_run_dir()
+    ARGS = _parse_args(sys.argv[1:])
     with atest_execution_info.AtestExecutionInfo(sys.argv[1:],
-                                                 RESULTS_DIR) as result_file:
+                                                 RESULTS_DIR,
+                                                 ARGS) as result_file:
         metrics_base.MetricsBase.tool_name = constants.TOOL_NAME
-        EXIT_CODE = main(sys.argv[1:], RESULTS_DIR)
+        EXIT_CODE = main(sys.argv[1:], RESULTS_DIR, ARGS)
         DETECTOR = bug_detector.BugDetector(sys.argv[1:], EXIT_CODE)
         metrics.LocalDetectEvent(
             detect_type=constants.DETECT_TYPE_BUG_DETECTED,
             result=DETECTOR.caught_result)
         if result_file:
-            print('Execution detail has saved in %s' % result_file.name)
+            print("Run 'atest --history' to review test result history.")
     sys.exit(EXIT_CODE)
diff --git a/atest/atest_execution_info.py b/atest/atest_execution_info.py
index a92fe1f..39cd3c1 100644
--- a/atest/atest_execution_info.py
+++ b/atest/atest_execution_info.py
@@ -100,6 +100,25 @@
                 pass
 
 
+def has_non_test_options(args):
+    """
+    check whether non-test option in the args.
+
+    Args:
+        args: An argspace.Namespace class instance holding parsed args.
+
+    Returns:
+        True, if args has at least one non-test option.
+        False, otherwise.
+    """
+    return (args.collect_tests_only
+            or args.dry_run
+            or args.help
+            or args.history
+            or args.info
+            or args.version)
+
+
 class AtestExecutionInfo(object):
     """Class that stores the whole test progress information in JSON format.
 
@@ -139,12 +158,13 @@
 
     result_reporters = []
 
-    def __init__(self, args, work_dir):
+    def __init__(self, args, work_dir, args_ns):
         """Initialise an AtestExecutionInfo instance.
 
         Args:
             args: Command line parameters.
-            work_dir : The directory for saving information.
+            work_dir: The directory for saving information.
+            args_ns: An argspace.Namespace class instance holding parsed args.
 
         Returns:
                A json format string.
@@ -152,6 +172,7 @@
         self.args = args
         self.work_dir = work_dir
         self.result_file = None
+        self.args_ns = args_ns
 
     def __enter__(self):
         """Create and return information file object."""
@@ -168,7 +189,8 @@
             self.result_file.write(AtestExecutionInfo.
                                    _generate_execution_detail(self.args))
             self.result_file.close()
-            symlink_latest_result(self.work_dir)
+            if not has_non_test_options(self.args_ns):
+                symlink_latest_result(self.work_dir)
         main_module = sys.modules.get(_MAIN_MODULE_KEY)
         main_exit_code = getattr(main_module, _EXIT_CODE_ATTR,
                                  constants.EXIT_CODE_ERROR)
diff --git a/atest/atest_utils.py b/atest/atest_utils.py
index 9a7178e..f1be007 100644
--- a/atest/atest_utils.py
+++ b/atest/atest_utils.py
@@ -16,6 +16,7 @@
 Utility functions for atest.
 """
 
+
 from __future__ import print_function
 
 import hashlib
@@ -36,13 +37,6 @@
 from metrics import metrics_base
 from metrics import metrics_utils
 
-try:
-    # If PYTHON2
-    from urllib2 import urlopen
-except ImportError:
-    metrics_utils.handle_exc_and_send_exit_event(
-        constants.IMPORT_FAILURE)
-    from urllib.request import urlopen
 
 _BASH_RESET_CODE = '\033[0m\n'
 # Arbitrary number to limit stdout for failed runs in _run_limited_output.
@@ -204,6 +198,13 @@
     # TODO: Also check if we have a slow connection to result server.
     if constants.RESULT_SERVER:
         try:
+            try:
+                # If PYTHON2
+                from urllib2 import urlopen
+            except ImportError:
+                metrics_utils.handle_exc_and_send_exit_event(
+                    constants.IMPORT_FAILURE)
+                from urllib.request import urlopen
             urlopen(constants.RESULT_SERVER,
                     timeout=constants.RESULT_SERVER_TIMEOUT).close()
             return True
diff --git a/atest/constants_default.py b/atest/constants_default.py
index 80581db..1df9e2d 100644
--- a/atest/constants_default.py
+++ b/atest/constants_default.py
@@ -182,14 +182,14 @@
 # TreeHugger TEST_MAPPING SUITE_PLANS
 TEST_MAPPING_SUITES = ['device-tests', 'general-tests']
 
-# VTS TF
-VTS_TF_MODULE = 'vts-tradefed'
+# VTS10 TF
+VTS_TF_MODULE = 'vts10-tradefed'
 
-# VTS-Core TF
-VTS_CORE_TF_MODULE = 'vts-core-tradefed'
+# VTS TF
+VTS_CORE_TF_MODULE = 'vts-tradefed'
 
 # VTS suite set
-VTS_CORE_SUITE = 'vts-core'
+VTS_CORE_SUITE = 'vts'
 
 # ATest TF
 ATEST_TF_MODULE = 'atest-tradefed'
diff --git a/atest/docs/developer_workflow.md b/atest/docs/developer_workflow.md
index 578ec1b..d3c2a32 100644
--- a/atest/docs/developer_workflow.md
+++ b/atest/docs/developer_workflow.md
@@ -6,7 +6,7 @@
 1. [Identify the code you should work on](#identify-the-code-you-should-work-on)
 2. [Working on the Python Code](#working-on-the-python-code)
 3. [Working on the TradeFed Code](#working-on-the-tradefed-code)
-4. [Working on the VTS-TradeFed Code](#working-on-the-vts-tradefed-code)
+4. [Working on the VTS10-TradeFed Code](#working-on-the-vts10-tradefed-code)
 5. [Working on the Robolectric Code](#working-on-the-robolectric-code)
 
 
@@ -44,7 +44,7 @@
 Each test runner will have a different workflow. Atest currently
 supports the following test runners:
 - TradeFed
-- VTS-TradeFed
+- VTS10-TradeFed
 - Robolectric
 
 
@@ -121,22 +121,22 @@
 
 Before submitting code you should run all the TradeFed tests.
 
-## <a name="working-on-the-vts-tradefed-code">Working on the VTS-TradeFed Code</a>
+## <a name="working-on-the-vts10-tradefed-code">Working on the VTS10-TradeFed Code</a>
 
-##### Where does the VTS-TradeFed code live?
+##### Where does the VTS10-TradeFed code live?
 
-The VTS-Tradefed code lives here: `test/vts/tools/vts-tradefed/`
+The VTS10-Tradefed code lives here: `test/vts/tools/vts-tradefed/`
 (path relative to android repo root)
 
 ##### Writing tests
 
-You shouldn't need to edit vts-tradefed code, so there is no
-need to write vts-tradefed tests. Reach out to the vts team
+You shouldn't need to edit vts10-tradefed code, so there is no
+need to write vts10 tests. Reach out to the vts team
 if you need information on their unittests.
 
 ##### Running tests
 
-Again, you shouldn't need to change vts-tradefed code.
+Again, you shouldn't need to change vts10-tradefed code.
 
 ## <a name="working-on-the-robolectric-code">Working on the Robolectric Code</a>
 
diff --git a/atest/module_info_unittest.py b/atest/module_info_unittest.py
index 2570ab2..4e48977 100755
--- a/atest/module_info_unittest.py
+++ b/atest/module_info_unittest.py
@@ -160,10 +160,10 @@
         self.assertFalse(mod_info.is_suite_in_compatibility_suites("cts", info))
         info2 = {'compatibility_suites': ["cts"]}
         self.assertTrue(mod_info.is_suite_in_compatibility_suites("cts", info2))
-        self.assertFalse(mod_info.is_suite_in_compatibility_suites("vts", info2))
-        info3 = {'compatibility_suites': ["cts", "vts"]}
+        self.assertFalse(mod_info.is_suite_in_compatibility_suites("vts10", info2))
+        info3 = {'compatibility_suites': ["cts", "vts10"]}
         self.assertTrue(mod_info.is_suite_in_compatibility_suites("cts", info3))
-        self.assertTrue(mod_info.is_suite_in_compatibility_suites("vts", info3))
+        self.assertTrue(mod_info.is_suite_in_compatibility_suites("vts10", info3))
         self.assertFalse(mod_info.is_suite_in_compatibility_suites("ats", info3))
 
     @mock.patch.object(module_info.ModuleInfo, 'is_testable_module')
diff --git a/atest/test_data/test_commands.json b/atest/test_data/test_commands.json
index bdd2390..2b6c008 100644
--- a/atest/test_data/test_commands.json
+++ b/atest/test_data/test_commands.json
@@ -36,7 +36,7 @@
 "atest_tradefed.sh template/atest_local_min --template:map test=atest --include-filter hello_world_test --log-level WARN --skip-loading-config-jar --logcat-on-failure --no-enable-granular-attempts"
 ], 
 "VtsCodelabHelloWorldTest": [
-"vts-tradefed run commandAndExit vts-staging-default -m VtsCodelabHelloWorldTest --skip-all-system-status-check --skip-preconditions --primary-abi-only"
+"vts10-tradefed run commandAndExit vts-staging-default -m VtsCodelabHelloWorldTest --skip-all-system-status-check --skip-preconditions --primary-abi-only"
 ], 
 "aidegen_unittests": [
 "atest_tradefed.sh template/atest_local_min --template:map test=atest --atest-log-file-path=/tmp/atest_run_1568627341_v33kdA/log --include-filter aidegen_unittests --log-level WARN"
@@ -56,4 +56,4 @@
 "CarMessengerRoboTests": [
 "./build/soong/soong_ui.bash --make-mode RunCarMessengerRoboTests"
 ]
-}
\ No newline at end of file
+}
diff --git a/atest/test_finders/module_finder.py b/atest/test_finders/module_finder.py
index f24e2ee..965688b 100644
--- a/atest/test_finders/module_finder.py
+++ b/atest/test_finders/module_finder.py
@@ -75,26 +75,26 @@
         return test_finder_utils.extract_test_from_tests(testable_modules)
 
     def _is_vts_module(self, module_name):
-        """Returns True if the module is a vts module, else False."""
+        """Returns True if the module is a vts10 module, else False."""
         mod_info = self.module_info.get_module_info(module_name)
         suites = []
         if mod_info:
             suites = mod_info.get('compatibility_suites', [])
         # Pull out all *ts (cts, tvts, etc) suites.
         suites = [suite for suite in suites if suite not in _SUITES_TO_IGNORE]
-        return len(suites) == 1 and 'vts' in suites
+        return len(suites) == 1 and 'vts10' in suites
 
     def _update_to_vts_test_info(self, test):
-        """Fill in the fields with vts specific info.
+        """Fill in the fields with vts10 specific info.
 
-        We need to update the runner to use the vts runner and also find the
+        We need to update the runner to use the vts10 runner and also find the
         test specific dependencies.
 
         Args:
-            test: TestInfo to update with vts specific details.
+            test: TestInfo to update with vts10 specific details.
 
         Return:
-            TestInfo that is ready for the vts test runner.
+            TestInfo that is ready for the vts10 test runner.
         """
         test.test_runner = self._VTS_TEST_RUNNER
         config_file = os.path.join(self.root_dir,
@@ -106,7 +106,7 @@
         # If we're not an absolute custom out dir, get relative out dir path.
         if custom_out_dir is None or not os.path.isabs(custom_out_dir):
             out_dir = os.path.relpath(out_dir, self.root_dir)
-        vts_out_dir = os.path.join(out_dir, 'vts', 'android-vts', 'testcases')
+        vts_out_dir = os.path.join(out_dir, 'vts10', 'android-vts10', 'testcases')
         # Parse dependency of default staging plans.
         xml_paths = test_finder_utils.search_integration_dirs(
             constants.VTS_STAGING_PLAN,
@@ -116,7 +116,7 @@
         for xml_path in xml_paths:
             vts_xmls |= test_finder_utils.get_plans_from_vts_xml(xml_path)
         for config_file in vts_xmls:
-            # Add in vts test build targets.
+            # Add in vts10 test build targets.
             test.build_targets |= test_finder_utils.get_targets_from_vts_xml(
                 config_file, vts_out_dir, self.module_info)
         test.build_targets.add('vts-test-core')
@@ -139,7 +139,7 @@
     def _process_test_info(self, test):
         """Process the test info and return some fields updated/changed.
 
-        We need to check if the test found is a special module (like vts) and
+        We need to check if the test found is a special module (like vts10) and
         update the test_info fields (like test_runner) appropriately.
 
         Args:
@@ -156,7 +156,7 @@
         test.module_class = mod_info['class']
         test.install_locations = test_finder_utils.get_install_locations(
             mod_info['installed'])
-        # Check if this is only a vts module.
+        # Check if this is only a vts10 module.
         if self._is_vts_module(test.test_name):
             return self._update_to_vts_test_info(test)
         elif self.module_info.is_robolectric_test(test.test_name):
diff --git a/atest/test_finders/module_finder_unittest.py b/atest/test_finders/module_finder_unittest.py
index e7cbf00..6d11bec 100755
--- a/atest/test_finders/module_finder_unittest.py
+++ b/atest/test_finders/module_finder_unittest.py
@@ -91,11 +91,11 @@
     def test_is_vts_module(self):
         """Test _load_module_info_file regular operation."""
         mod_name = 'mod'
-        is_vts_module_info = {'compatibility_suites': ['vts', 'tests']}
+        is_vts_module_info = {'compatibility_suites': ['vts10', 'tests']}
         self.mod_finder.module_info.get_module_info.return_value = is_vts_module_info
         self.assertTrue(self.mod_finder._is_vts_module(mod_name))
 
-        is_not_vts_module = {'compatibility_suites': ['vts', 'cts']}
+        is_not_vts_module = {'compatibility_suites': ['vts10', 'cts']}
         self.mod_finder.module_info.get_module_info.return_value = is_not_vts_module
         self.assertFalse(self.mod_finder._is_vts_module(mod_name))
 
diff --git a/atest/test_finders/test_finder_utils.py b/atest/test_finders/test_finder_utils.py
index 2e8ec64..a6a2475 100644
--- a/atest/test_finders/test_finder_utils.py
+++ b/atest/test_finders/test_finder_utils.py
@@ -586,7 +586,7 @@
 
     We're going to pull the following bits of info:
       - Parse any .apk files listed in the config file.
-      - Parse option value for "test-module-name" (for vts tests).
+      - Parse option value for "test-module-name" (for vts10 tests).
       - Look for the perf script.
 
     Args:
@@ -627,14 +627,14 @@
 
 
 def _get_vts_push_group_targets(push_file, rel_out_dir):
-    """Retrieve vts push group build targets.
+    """Retrieve vts10 push group build targets.
 
     A push group file is a file that list out test dependencies and other push
     group files. Go through the push file and gather all the test deps we need.
 
     Args:
         push_file: Name of the push file in the VTS
-        rel_out_dir: Abs path to the out dir to help create vts build targets.
+        rel_out_dir: Abs path to the out dir to help create vts10 build targets.
 
     Returns:
         Set of string which represent build targets.
@@ -678,7 +678,7 @@
 
 
 def _get_vts_binary_src_target(value, rel_out_dir):
-    """Parse out the vts binary src target.
+    """Parse out the vts10 binary src target.
 
     The value can be in the following pattern:
       - {_32bit,_64bit,_IPC32_32bit}::DATA/target (DATA/target)
@@ -727,9 +727,9 @@
     option_tags = xml_root.findall('.//include')
     if not option_tags:
         return plans
-    # Currently, all vts xmls live in the same dir :
+    # Currently, all vts10 xmls live in the same dir :
     # https://android.googlesource.com/platform/test/vts/+/master/tools/vts-tradefed/res/config/
-    # If the vts plans start using folders to organize the plans, the logic here
+    # If the vts10 plans start using folders to organize the plans, the logic here
     # should be changed.
     xml_dir = os.path.dirname(xml_file)
     for tag in option_tags:
@@ -739,9 +739,9 @@
 
 
 def get_targets_from_vts_xml(xml_file, rel_out_dir, module_info):
-    """Parse a vts xml for test dependencies we need to build.
+    """Parse a vts10 xml for test dependencies we need to build.
 
-    We have a separate vts parsing function because we make a big assumption
+    We have a separate vts10 parsing function because we make a big assumption
     on the targets (the way they're formatted and what they represent) and we
     also create these build targets in a very special manner as well.
     The 6 options we're looking for are:
@@ -754,7 +754,7 @@
 
     Args:
         module_info: ModuleInfo class used to verify targets are valid modules.
-        rel_out_dir: Abs path to the out dir to help create vts build targets.
+        rel_out_dir: Abs path to the out dir to help create vts10 build targets.
         xml_file: abs path to xml file.
 
     Returns:
@@ -770,7 +770,7 @@
             if module_info.is_module(value):
                 targets.add(value)
             else:
-                logging.warning('vts test module (%s) not present in module '
+                logging.warning('vts10 test module (%s) not present in module '
                                 'info, skipping build', value)
         elif name == _VTS_BINARY_SRC:
             targets.add(_get_vts_binary_src_target(value, rel_out_dir))
@@ -797,7 +797,7 @@
             # and then append the _VTS_TEST_FILE value to targets to build.
             target = os.path.join(rel_out_dir, value)
             # If value is just an APK, specify the path that we expect it to be in
-            # e.g. out/host/linux-x86/vts/android-vts/testcases/DATA/app/test_file/test_file.apk
+            # e.g. out/host/linux-x86/vts10/android-vts10/testcases/DATA/app/test_file/test_file.apk
             head, _ = os.path.split(value)
             if not head:
                 target = os.path.join(rel_out_dir, _VTS_OUT_DATA_APP_PATH,
diff --git a/atest/test_finders/test_info.py b/atest/test_finders/test_info.py
index d872576..707f49a 100644
--- a/atest/test_finders/test_info.py
+++ b/atest/test_finders/test_info.py
@@ -47,7 +47,7 @@
             test_finder: String of test finder.
             compatibility_suites: A list of compatibility_suites. It's a
                         snippet of compatibility_suites in module_info. e.g.
-                        ["device-tests",  "vts-core"]
+                        ["device-tests",  "vts10"]
         """
         self.test_name = test_name
         self.test_runner = test_runner
diff --git a/atest/test_runners/atest_tf_test_runner.py b/atest/test_runners/atest_tf_test_runner.py
index 5380423..6f26c6d 100644
--- a/atest/test_runners/atest_tf_test_runner.py
+++ b/atest/test_runners/atest_tf_test_runner.py
@@ -107,7 +107,7 @@
         key_path = os.path.join(self.root_dir, ape_api_key)
         if ape_api_key and os.path.exists(key_path):
             logging.debug('Set APE_API_KEY: %s', ape_api_key)
-            os.environ['APE_API_KEY'] = ape_api_key
+            os.environ['APE_API_KEY'] = key_path
         else:
             logging.debug('APE_API_KEY not set, some GTS tests may fail'
                           ' without authentication.')
diff --git a/atest/test_runners/atest_tf_test_runner_unittest.py b/atest/test_runners/atest_tf_test_runner_unittest.py
index d1968e8..5344ba0 100755
--- a/atest/test_runners/atest_tf_test_runner_unittest.py
+++ b/atest/test_runners/atest_tf_test_runner_unittest.py
@@ -306,13 +306,13 @@
         self.tr._try_set_gts_authentication_key()
         mock_exist.assert_not_called()
 
-    @mock.patch('constants.GTS_GOOGLE_SERVICE_ACCOUNT')
-    @mock.patch('os.path.exists')
-    def test_try_set_gts_authentication_key_not_set(self, mock_exist, mock_key):
+    @mock.patch('os.path.join', return_value='/tmp/file_not_exist.json')
+    def test_try_set_gts_authentication_key_not_set(self, _):
         """Test try_set_authentication_key_not_set method."""
-        # Test key neither exists nor set by user.
-        mock_exist.return_value = False
-        mock_key.return_value = ''
+        # Delete the environment variable if it's set. This is fine for this
+        # method because it's for validating the APE_API_KEY isn't set.
+        if os.environ.get('APE_API_KEY'):
+            del os.environ['APE_API_KEY']
         self.tr._try_set_gts_authentication_key()
         self.assertEqual(os.environ.get('APE_API_KEY'), None)
 
diff --git a/atest/test_runners/vts_tf_test_runner.py b/atest/test_runners/vts_tf_test_runner.py
index 9e6801b..c1f53e0 100644
--- a/atest/test_runners/vts_tf_test_runner.py
+++ b/atest/test_runners/vts_tf_test_runner.py
@@ -28,15 +28,15 @@
 class VtsTradefedTestRunner(atest_tf_test_runner.AtestTradefedTestRunner):
     """TradeFed Test Runner class."""
     NAME = 'VtsTradefedTestRunner'
-    EXECUTABLE = 'vts-tradefed'
+    EXECUTABLE = 'vts10-tradefed'
     _RUN_CMD = ('{exe} run commandAndExit {plan} -m {test} {args}')
-    _BUILD_REQ = {'vts-tradefed-standalone'}
+    _BUILD_REQ = {'vts10-tradefed-standalone'}
     _DEFAULT_ARGS = ['--skip-all-system-status-check',
                      '--skip-preconditions',
                      '--primary-abi-only']
 
     def __init__(self, results_dir, **kwargs):
-        """Init stuff for vts tradefed runner class."""
+        """Init stuff for vts10 tradefed runner class."""
         super(VtsTradefedTestRunner, self).__init__(results_dir, **kwargs)
         self.run_cmd_dict = {'exe': self.EXECUTABLE,
                              'test': '',
@@ -74,10 +74,10 @@
         return ret_code
 
     def _parse_extra_args(self, extra_args):
-        """Convert the extra args into something vts-tf can understand.
+        """Convert the extra args into something vts10-tf can understand.
 
         We want to transform the top-level args from atest into specific args
-        that vts-tradefed supports. The only arg we take as is is EXTRA_ARG
+        that vts10-tradefed supports. The only arg we take as is is EXTRA_ARG
         since that is what the user intentionally wants to pass to the test
         runner.
 
@@ -114,7 +114,7 @@
             extra_args: Dict of extra args to add to test run.
 
         Returns:
-            A List of strings that contains the vts-tradefed run command.
+            A List of strings that contains the vts10-tradefed run command.
         """
         cmds = []
         args = self._DEFAULT_ARGS
diff --git a/atest_tradefed.sh b/atest_tradefed.sh
index 4b66fdb..eb6cf53 100755
--- a/atest_tradefed.sh
+++ b/atest_tradefed.sh
@@ -34,7 +34,7 @@
           hosttestlib.jar
           cts-tradefed.jar
           sts-tradefed.jar
-          vts-core-tradefed.jar
+          vts-tradefed.jar
           vts10-tradefed.jar
           csuite-harness.jar
           tradefed-isolation.jar
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.