1#!/usr/bin/env python3
2#
3#   Copyright 2016 - The Android Open Source Project
4#
5#   Licensed under the Apache License, Version 2.0 (the "License");
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of 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,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16
17from acts.controllers.android_device import AndroidDevice
18from acts.controllers.utils_lib import host_utils
19import acts.controllers.native as native
20from subprocess import call
21
22import logging
23import time
24
25#TODO(tturney): Merge this into android device
26
27MOBLY_CONTROLLER_CONFIG_NAME = "NativeAndroidDevice"
28ACTS_CONTROLLER_REFERENCE_NAME = "native_android_devices"
29
30
31def create(configs):
32    logger = logging
33    ads = get_instances(configs)
34    for ad in ads:
35        try:
36            ad.get_droid()
37        except:
38            logger.exception("Failed to start sl4n on %s" % ad.serial)
39    return ads
40
41
42def destroy(ads):
43    pass
44
45
46def get_instances(serials, ):
47    """Create AndroidDevice instances from a list of serials.
48
49    Args:
50        serials: A list of android device serials.
51        logger: A logger to be passed to each instance.
52
53    Returns:
54        A list of AndroidDevice objects.
55    """
56    results = []
57    for s in serials:
58        results.append(NativeAndroidDevice(s))
59    return results
60
61
62class NativeAndroidDeviceError(Exception):
63    pass
64
65
66class NativeAndroidDevice(AndroidDevice):
67    def __del__(self):
68        if self.h_port:
69            self.adb.forward("--remove tcp:%d" % self.h_port)
70
71    def get_droid(self, handle_event=True):
72        """Create an sl4n connection to the device.
73
74        Return the connection handler 'droid'. By default, another connection
75        on the same session is made for EventDispatcher, and the dispatcher is
76        returned to the caller as well.
77        If sl4n server is not started on the device, try to start it.
78
79        Args:
80            handle_event: True if this droid session will need to handle
81                events.
82
83        Returns:
84            droid: Android object useds to communicate with sl4n on the android
85                device.
86            ed: An optional EventDispatcher to organize events for this droid.
87
88        Examples:
89            Don't need event handling:
90            >>> ad = NativeAndroidDevice()
91            >>> droid = ad.get_droid(False)
92
93            Need event handling:
94            >>> ad = NativeAndroidDevice()
95            >>> droid, ed = ad.get_droid()
96        """
97        if not self.h_port or not host_utils.is_port_available(self.h_port):
98            self.h_port = host_utils.get_available_host_port()
99        self.adb.tcp_forward(self.h_port, self.d_port)
100        pid = self.adb.shell("pidof -s sl4n", ignore_status=True)
101        while (pid):
102            self.adb.shell("kill {}".format(pid))
103            pid = self.adb.shell("pidof -s sl4n", ignore_status=True)
104        call(
105            ["adb -s " + self.serial + " shell sh -c \"/system/bin/sl4n\" &"],
106            shell=True)
107        try:
108            time.sleep(3)
109            droid = self.start_new_session()
110        except:
111            droid = self.start_new_session()
112        return droid
113
114    def start_new_session(self):
115        """Start a new session in sl4n.
116
117        Also caches the droid in a dict with its uid being the key.
118
119        Returns:
120            An Android object used to communicate with sl4n on the android
121                device.
122
123        Raises:
124            sl4nException: Something is wrong with sl4n and it returned an
125            existing uid to a new session.
126        """
127        droid = native.NativeAndroid(port=self.h_port)
128        droid.open()
129        if droid.uid in self._droid_sessions:
130            raise bt.SL4NException(("SL4N returned an existing uid for a "
131                                    "new session. Abort."))
132            return droid
133        self._droid_sessions[droid.uid] = [droid]
134        return droid
135