Deduplicate the mock adb proxy. (#598)

diff --git a/tests/lib/mock_android_device.py b/tests/lib/mock_android_device.py
index 443f838..dd1ae15 100755
--- a/tests/lib/mock_android_device.py
+++ b/tests/lib/mock_android_device.py
@@ -15,10 +15,22 @@
 # This module has common mock objects and functions used in unit tests for
 # mobly.controllers.android_device module.
 
+from builtins import bytes
+
 import logging
 import mock
 import os
 
+DEFAULT_MOCK_PROPERTIES = {
+    'ro.build.id': 'AB42',
+    'ro.build.type': 'userdebug',
+    'ro.build.product': 'FakeModel',
+    'ro.build.version.codename': 'Z',
+    'ro.build.version.sdk': '28',
+    'ro.product.name': 'FakeModel',
+    'sys.boot_completed': "1",
+}
+
 
 class Error(Exception):
     pass
@@ -64,17 +76,27 @@
 class MockAdbProxy(object):
     """Mock class that swaps out calls to adb with mock calls."""
 
-    def __init__(self, serial, fail_br=False, fail_br_before_N=False):
+    def __init__(self,
+                 serial='',
+                 fail_br=False,
+                 fail_br_before_N=False,
+                 mock_properties=None,
+                 installed_packages=None,
+                 instrumented_packages=None):
         self.serial = serial
         self.fail_br = fail_br
         self.fail_br_before_N = fail_br_before_N
-        self.mock_properties = {
-            "ro.build.id": "AB42",
-            "ro.build.type": "userdebug",
-            "ro.build.product": "FakeModel",
-            "ro.product.name": "FakeModel",
-            "sys.boot_completed": "1"
-        }
+        if mock_properties is None:
+            self.mock_properties = DEFAULT_MOCK_PROPERTIES
+        else:
+            self.mock_properties = mock_properties
+        if installed_packages is None:
+            installed_packages = []
+        self.installed_packages = installed_packages
+        if instrumented_packages is None:
+            instrumented_packages = []
+        self.installed_packages = installed_packages
+        self.instrumented_packages = instrumented_packages
 
     def shell(self, params, timeout=None):
         if params == "id -u":
@@ -87,6 +109,19 @@
             if self.fail_br_before_N:
                 return b"/system/bin/sh: bugreportz: not found"
             return b'1.1'
+        elif 'pm list package' in params:
+            packages = self.installed_packages + [
+                package for package, _, _ in self.instrumented_packages
+            ]
+            return bytes('\n'.join(
+                ['package:%s' % package for package in packages]), 'utf-8')
+        elif 'pm list instrumentation' in params:
+            return bytes('\n'.join([
+                'instrumentation:%s/%s (target=%s)' % (package, runner, target)
+                for package, runner, target in self.instrumented_packages
+            ]), 'utf-8')
+        elif 'which' in params:
+            return b''
 
     def getprop(self, params):
         if params in self.mock_properties:
diff --git a/tests/mobly/controllers/android_device_lib/sl4a_client_test.py b/tests/mobly/controllers/android_device_lib/sl4a_client_test.py
index bc06220..e3db6a4 100755
--- a/tests/mobly/controllers/android_device_lib/sl4a_client_test.py
+++ b/tests/mobly/controllers/android_device_lib/sl4a_client_test.py
@@ -22,34 +22,7 @@
 from mobly.controllers.android_device_lib import jsonrpc_client_base
 from mobly.controllers.android_device_lib import sl4a_client
 from tests.lib import jsonrpc_client_test_base
-
-
-class MockAdbProxy(object):
-    def __init__(self, **kwargs):
-        self.apk_not_installed = kwargs.get('apk_not_installed', False)
-
-    def shell(self, params, shell=False):
-        if 'pm list package' in params:
-            if self.apk_not_installed:
-                return bytes('', 'utf-8')
-            return bytes('package:com.googlecode.android_scripting', 'utf-8')
-
-    def getprop(self, params):
-        if params == 'ro.build.version.codename':
-            return 'Z'
-        elif params == 'ro.build.version.sdk':
-            return '28'
-
-    def __getattr__(self, name):
-        """All calls to the none-existent functions in adb proxy would
-        simply return the adb command string.
-        """
-
-        def adb_call(*args):
-            arg_str = ' '.join(str(elem) for elem in args)
-            return arg_str
-
-        return adb_call
+from tests.lib import mock_android_device
 
 
 class Sl4aClientTest(jsonrpc_client_test_base.JsonRpcClientTestBase):
@@ -82,14 +55,15 @@
         self.setup_mock_socket_file(mock_create_connection)
         self._setup_mock_instrumentation_cmd(
             mock_start_standing_subprocess, resp_lines=[b'\n'])
-        client = self._make_client(adb_proxy=MockAdbProxy(
-            apk_not_installed=True))
+        client = self._make_client(
+            adb_proxy=mock_android_device.MockAdbProxy())
         with self.assertRaisesRegex(jsonrpc_client_base.AppStartError,
                                     '.* SL4A is not installed on .*'):
             client.start_app_and_connect()
 
     def _make_client(self, adb_proxy=None):
-        adb_proxy = adb_proxy or MockAdbProxy()
+        adb_proxy = adb_proxy or mock_android_device.MockAdbProxy(
+            installed_packages=['com.googlecode.android_scripting'])
         ad = mock.Mock()
         ad.adb = adb_proxy
         return sl4a_client.Sl4aClient(ad=ad)
diff --git a/tests/mobly/controllers/android_device_lib/snippet_client_test.py b/tests/mobly/controllers/android_device_lib/snippet_client_test.py
index 4f9e441..8b12a9f 100755
--- a/tests/mobly/controllers/android_device_lib/snippet_client_test.py
+++ b/tests/mobly/controllers/android_device_lib/snippet_client_test.py
@@ -23,6 +23,7 @@
 from mobly.controllers.android_device_lib import jsonrpc_client_base
 from mobly.controllers.android_device_lib import snippet_client
 from tests.lib import jsonrpc_client_test_base
+from tests.lib import mock_android_device
 
 MOCK_PACKAGE_NAME = 'some.package.name'
 MOCK_MISSING_PACKAGE_NAME = 'not.installed'
@@ -41,51 +42,6 @@
         return '__builtin__.print'
 
 
-class MockAdbProxy(object):
-    def __init__(self, **kwargs):
-        self.apk_not_installed = kwargs.get('apk_not_installed', False)
-        self.apk_not_instrumented = kwargs.get('apk_not_instrumented', False)
-        self.target_not_installed = kwargs.get('target_not_installed', False)
-
-    def shell(self, params, shell=False):
-        if 'pm list package' in params:
-            if self.apk_not_installed:
-                return b''
-            if self.target_not_installed and MOCK_MISSING_PACKAGE_NAME in params:
-                return b''
-            return bytes('package:%s' % MOCK_PACKAGE_NAME, 'utf-8')
-        elif 'pm list instrumentation' in params:
-            if self.apk_not_instrumented:
-                return b''
-            if self.target_not_installed:
-                return bytes('instrumentation:{p}/{r} (target={mp})'.format(
-                    p=MOCK_PACKAGE_NAME,
-                    r=snippet_client._INSTRUMENTATION_RUNNER_PACKAGE,
-                    mp=MOCK_MISSING_PACKAGE_NAME), 'utf-8')
-            return bytes('instrumentation:{p}/{r} (target={p})'.format(
-                p=MOCK_PACKAGE_NAME,
-                r=snippet_client._INSTRUMENTATION_RUNNER_PACKAGE), 'utf-8')
-        elif 'which' in params:
-            return b''
-
-    def getprop(self, params):
-        if params == 'ro.build.version.codename':
-            return 'Z'
-        elif params == 'ro.build.version.sdk':
-            return '28'
-
-    def __getattr__(self, name):
-        """All calls to the none-existent functions in adb proxy would
-        simply return the adb command string.
-        """
-
-        def adb_call(*args):
-            arg_str = ' '.join(str(elem) for elem in args)
-            return arg_str
-
-        return adb_call
-
-
 class SnippetClientTest(jsonrpc_client_test_base.JsonRpcClientTestBase):
     """Unit tests for mobly.controllers.android_device_lib.snippet_client.
     """
@@ -95,14 +51,16 @@
         sc._check_app_installed()
 
     def test_check_app_installed_fail_app_not_installed(self):
-        sc = self._make_client(MockAdbProxy(apk_not_installed=True))
+        sc = self._make_client(mock_android_device.MockAdbProxy())
         expected_msg = '.* %s is not installed.' % MOCK_PACKAGE_NAME
         with self.assertRaisesRegex(snippet_client.AppStartPreCheckError,
                                     expected_msg):
             sc._check_app_installed()
 
     def test_check_app_installed_fail_not_instrumented(self):
-        sc = self._make_client(MockAdbProxy(apk_not_instrumented=True))
+        sc = self._make_client(
+            mock_android_device.MockAdbProxy(
+                installed_packages=[MOCK_PACKAGE_NAME]))
         expected_msg = ('.* %s is installed, but it is not instrumented.' %
                         MOCK_PACKAGE_NAME)
         with self.assertRaisesRegex(snippet_client.AppStartPreCheckError,
@@ -110,7 +68,10 @@
             sc._check_app_installed()
 
     def test_check_app_installed_fail_target_not_installed(self):
-        sc = self._make_client(MockAdbProxy(target_not_installed=True))
+        sc = self._make_client(
+            mock_android_device.MockAdbProxy(instrumented_packages=[(
+                MOCK_PACKAGE_NAME, snippet_client.
+                _INSTRUMENTATION_RUNNER_PACKAGE, MOCK_MISSING_PACKAGE_NAME)]))
         expected_msg = ('.* Instrumentation target %s is not installed.' %
                         MOCK_MISSING_PACKAGE_NAME)
         with self.assertRaisesRegex(snippet_client.AppStartPreCheckError,
@@ -485,7 +446,10 @@
         mock_print.assert_not_called()
 
     def _make_client(self, adb_proxy=None):
-        adb_proxy = adb_proxy or MockAdbProxy()
+        adb_proxy = adb_proxy or mock_android_device.MockAdbProxy(
+            instrumented_packages=[(MOCK_PACKAGE_NAME, snippet_client.
+                                    _INSTRUMENTATION_RUNNER_PACKAGE,
+                                    MOCK_PACKAGE_NAME)])
         ad = mock.Mock()
         ad.adb = adb_proxy
         return snippet_client.SnippetClient(package=MOCK_PACKAGE_NAME, ad=ad)