1#!/usr/bin/env python3
2#
3# Copyright (C) 2016 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may not
6# use this file except in compliance with the License. You may obtain a copy of
7# the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations under
15# the License.
16
17import logging
18
19import mobly.controllers.android_device_lib.sl4a_client as sl4a_client
20from mobly.controllers.android_device import AndroidDevice
21from mobly.controllers.android_device_lib.adb import AdbError
22from mobly.controllers.android_device_lib.jsonrpc_client_base import AppRestoreConnectionError
23from mobly.controllers.android_device_lib.services.sl4a_service import Sl4aService
24
25
26class FakeFuture:
27    """
28    A fake Future object to override default mobly behavior
29    """
30
31    def set_result(self, result):
32        logging.debug("Setting fake result {}".format(result))
33
34
35def setup_sl4a(device: AndroidDevice, server_port: int, forwarded_port: int):
36    """
37    A method that setups up SL4A instance on mobly
38    :param device: an AndroidDevice instance
39    :param server_port: Preferred server port used by SL4A on Android device
40    :param forwarded_port: Preferred server port number forwarded from Android to
41                           the host PC via adb for SL4A connections
42    :return: None
43    """
44    sl4a_client._DEVICE_SIDE_PORT = server_port
45    sl4a_client._APP_START_WAIT_TIME = 0.5
46    if device.sl4a is not None:
47        device.log.error("SL4A is not none when registering")
48    device.services.register('sl4a', Sl4aService, start_service=False)
49    # Start the SL4A service and event dispatcher
50    try:
51        device.sl4a.start()
52    except AppRestoreConnectionError as exp:
53        device.log.debug("AppRestoreConnectionError {}".format(exp))
54    # Pause the dispatcher, but do not stop the service
55    try:
56        device.sl4a.pause()
57    except AdbError as exp:
58        device.log.debug("Failed to pause() {}".format(exp))
59    sl4a_client._APP_START_WAIT_TIME = 2 * 60
60    # Restart the service with a new host port
61    device.sl4a.restore_app_connection(port=forwarded_port)
62
63
64def teardown_sl4a(device: AndroidDevice):
65    """
66    A method to tear down SL4A interface on mobly
67    :param device: an AndroidDevice instance that already contains SL4a
68    :return: None
69    """
70    if device.sl4a.is_alive:
71        # Both self.dut.sl4a and self.dut.sl4a.ed._sl4a are Sl4aClient instances
72        # If we do not set host_port to None here, host_poart will be removed twice from Android
73        # The 2nd removal will trigger and exception that spam the test result
74        # TODO: Resolve this issue in mobly
75        device.log.info("Clearing host_port to prevent mobly crash {}".format(device.sl4a._sl4a_client.host_port))
76        device.sl4a._sl4a_client.host_port = None
77        # Moreover concurrent.Future.set_result() should never be called from thread that is
78        # waiting for the future. However, mobly calls it and cause InvalidStateError when it
79        # tries to do that after the thread pool has stopped, overriding it here
80        # TODO: Resolve this issue in mobly
81        try:
82            device.sl4a.ed.poller = FakeFuture()
83        except Exception as e:
84            print(e)
85    try:
86        # Guarded by is_alive internally
87        device.sl4a.stop()
88    except AdbError as exp:
89        device.log.warning("Failed top stop()".format(exp))
90