adb.py supports Android N+ versions of adb as well.

Bug: 38512781
Fix: 38512781
Test: acts_adb_test.py
Test: act.py -c <> -tc BleScanApiTest

Change-Id: If8afc826c167239579cc0a22852d444b8392f181
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 3e39a38..ca749eb 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,5 +1,6 @@
 [Hook Scripts]
 acts_base_class_test = ./acts/framework/tests/acts_base_class_test.py
+acts_adb_test = ./acts/framework/tests/acts_adb_test.py
 acts_android_device_test = ./acts/framework/tests/acts_android_device_test.py
 acts_asserts_test = ./acts/framework/tests/acts_asserts_test.py
 acts_base_class_test = ./acts/framework/tests/acts_base_class_test.py
diff --git a/acts/framework/acts/controllers/adb.py b/acts/framework/acts/controllers/adb.py
index 2a1b69d..68c96de 100644
--- a/acts/framework/acts/controllers/adb.py
+++ b/acts/framework/acts/controllers/adb.py
@@ -29,6 +29,9 @@
 
 DEFAULT_ADB_TIMEOUT = 60
 DEFAULT_ADB_PULL_TIMEOUT = 180
+# Uses a regex to be backwards compatible with previous versions of ADB
+# (N and above add the serial to the error msg).
+DEVICE_NOT_FOUND_REGEX = re.compile('^error: device (?:\'.*?\' )?not found')
 
 
 def parsing_parcel_output(output):
@@ -107,31 +110,28 @@
     def _exec_cmd(self, cmd, ignore_status=False, timeout=DEFAULT_ADB_TIMEOUT):
         """Executes adb commands in a new shell.
 
-        This is specific to executing adb binary because stderr is not a good
-        indicator of cmd execution status.
+        This is specific to executing adb commands.
 
         Args:
-            cmds: A string that is the adb command to execute.
+            cmd: A string that is the adb command to execute.
 
         Returns:
-            The output of the adb command run if exit code is 0.
+            The stdout of the adb command.
 
         Raises:
-            AdbError is raised if the adb command exit code is not 0.
+            AdbError is raised if adb cannot find the device.
         """
         result = job.run(cmd, ignore_status=True, timeout=timeout)
         ret, out, err = result.exit_status, result.stdout, result.stderr
 
         logging.debug("cmd: %s, stdout: %s, stderr: %s, ret: %s", cmd, out,
                       err, ret)
-
-        if ret == 0 or ignore_status:
-            if "Result: Parcel" in out:
-                return parsing_parcel_output(out)
-            else:
-                return out
-        else:
+        if not ignore_status and ret == 1 and DEVICE_NOT_FOUND_REGEX.match(err):
             raise AdbError(cmd=cmd, stdout=out, stderr=err, ret_code=ret)
+        elif "Result: Parcel" in out:
+            return parsing_parcel_output(out)
+        else:
+            return out
 
     def _exec_adb_cmd(self, name, arg_str, **kwargs):
         return self._exec_cmd(' '.join((self.adb_str, name, arg_str)),
diff --git a/acts/framework/acts/controllers/android_device.py b/acts/framework/acts/controllers/android_device.py
index b5f00da..72c1b76 100755
--- a/acts/framework/acts/controllers/android_device.py
+++ b/acts/framework/acts/controllers/android_device.py
@@ -776,17 +776,12 @@
         True if package is installed. False otherwise.
         """
         try:
-            out = self.adb.shell(
-                'pm list packages | grep -w "package:%s"' % package_name,
-                ignore_status=False)
-            if package_name in out:
-                self.log.debug("apk %s is installed", package_name)
-                return True
-            else:
-                self.log.debug("apk %s is not installed, query returns %s",
-                               package_name, out)
-                return False
-        except:
+            return not self.adb.shell(
+                'pm list packages | grep -w "package:%s"' % package_name)
+
+        except Exception as err:
+            self.log.error('Could not determine if %s is installed. '
+                           'Received error:\n%s', package_name, err)
             return False
 
     def is_sl4a_installed(self):
diff --git a/acts/framework/tests/acts_adb_test.py b/acts/framework/tests/acts_adb_test.py
new file mode 100755
index 0000000..be2535a
--- /dev/null
+++ b/acts/framework/tests/acts_adb_test.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import unittest
+import mock
+from acts.controllers.adb import AdbProxy
+from acts.controllers.adb import AdbError
+
+
+class MockJob(object):
+    def __init__(self, exit_status=0, stderr='', stdout=''):
+        self.exit_status = exit_status
+        self.stderr = stderr
+        self.stdout = stdout
+
+
+class MockAdbProxy(AdbProxy):
+    def __init__(self):
+        pass
+
+
+class ADBTest(unittest.TestCase):
+    """A class for testing acts/controllers/adb.py"""
+
+    def test__exec_cmd_failure_old_adb(self):
+        mock_job = MockJob(exit_status=1, stderr='error: device not found')
+        cmd = ['adb', '-s', '"SOME_SERIAL"', 'shell', '"SOME_SHELL_CMD"']
+        with mock.patch('acts.libs.proc.job.run', return_value=mock_job):
+            with self.assertRaises(AdbError):
+                MockAdbProxy()._exec_cmd(cmd)
+
+    def test__exec_cmd_failure_new_adb(self):
+        mock_job = MockJob(
+            exit_status=1, stderr='error: device \'DEADBEEF\' not found')
+        cmd = ['adb', '-s', '"SOME_SERIAL"', 'shell', '"SOME_SHELL_CMD"']
+        with mock.patch('acts.libs.proc.job.run', return_value=mock_job):
+            with self.assertRaises(AdbError):
+                MockAdbProxy()._exec_cmd(cmd)
+
+    def test__exec_cmd_pass_ret_1(self):
+        mock_job = MockJob(exit_status=1, stderr='error not related to adb')
+        cmd = ['adb', '-s', '"SOME_SERIAL"', 'shell', '"SOME_SHELL_CMD"']
+        with mock.patch('acts.libs.proc.job.run', return_value=mock_job):
+            MockAdbProxy()._exec_cmd(cmd)
+
+    def test__exec_cmd_pass_basic(self):
+        mock_job = MockJob(exit_status=0, stderr='', stdout='FEEDACAB')
+        cmd = ['adb', '-s', '"SOME_SERIAL"', 'shell', '"SOME_SHELL_CMD"']
+        with mock.patch('acts.libs.proc.job.run', return_value=mock_job):
+            MockAdbProxy()._exec_cmd(cmd)
+
+    def test__exec_cmd_pass_no_stdout(self):
+        mock_job = MockJob(exit_status=0, stderr='', stdout='')
+        cmd = ['adb', '-s', '"SOME_SERIAL"', 'shell', '"SOME_SHELL_CMD"']
+        with mock.patch('acts.libs.proc.job.run', return_value=mock_job):
+            MockAdbProxy()._exec_cmd(cmd)
+
+
+if __name__ == "__main__":
+    unittest.main()