• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2017 Google Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import time
16
17from mobly.controllers.android_device_lib import errors
18from mobly.controllers.android_device_lib import snippet_event
19
20# The max timeout cannot be larger than the max time the socket waits for a
21# response message. Otherwise, the socket would timeout before the Rpc call
22# does, leaving both server and client in unknown states.
23MAX_TIMEOUT = 60 * 10
24DEFAULT_TIMEOUT = 120  # two minutes
25
26
27class Error(errors.DeviceError):
28  pass
29
30
31class TimeoutError(Error):
32  pass
33
34
35class CallbackHandler:
36  """The class used to handle a specific group of callback events.
37
38  All the events handled by a CallbackHandler are originally triggered by one
39  async Rpc call. All the events are tagged with a callback_id specific to a
40  call to an AsyncRpc method defined on the server side.
41
42  The raw message representing an event looks like:
43
44  .. code-block:: python
45
46    {
47      'callbackId': <string, callbackId>,
48      'name': <string, name of the event>,
49      'time': <long, epoch time of when the event was created on the
50        server side>,
51      'data': <dict, extra data from the callback on the server side>
52    }
53
54  Each message is then used to create a SnippetEvent object on the client
55  side.
56
57  Attributes:
58    ret_value: The direct return value of the async Rpc call.
59  """
60
61  def __init__(self, callback_id, event_client, ret_value, method_name, ad):
62    self._id = callback_id
63    self._event_client = event_client
64    self.ret_value = ret_value
65    self._method_name = method_name
66    self._ad = ad
67
68  @property
69  def callback_id(self):
70    return self._id
71
72  def _callEventWaitAndGet(self, callback_id, event_name, timeout):
73    """Calls snippet lib's eventWaitAndGet.
74
75    Override this method to use this class with various snippet lib
76    implementations.
77
78    Args:
79      callback_id: The callback identifier.
80      event_name: The callback name.
81      timeout: The number of seconds to wait for the event.
82
83    Returns:
84      The event dictionary.
85    """
86    # Convert to milliseconds for Java side.
87    timeout_ms = int(timeout * 1000)
88    return self._event_client.eventWaitAndGet(callback_id, event_name,
89                                              timeout_ms)
90
91  def _callEventGetAll(self, callback_id, event_name):
92    """Calls snippet lib's eventGetAll.
93
94    Override this method to use this class with various snippet lib
95    implementations.
96
97    Args:
98      callback_id: The callback identifier.
99      event_name: The callback name.
100
101    Returns:
102      A list of event dictionaries.
103    """
104    return self._event_client.eventGetAll(callback_id, event_name)
105
106  def waitAndGet(self, event_name, timeout=DEFAULT_TIMEOUT):
107    """Blocks until an event of the specified name has been received and
108    return the event, or timeout.
109
110    Args:
111      event_name: string, name of the event to get.
112      timeout: float, the number of seconds to wait before giving up.
113
114    Returns:
115      SnippetEvent, the oldest entry of the specified event.
116
117    Raises:
118      Error: If the specified timeout is longer than the max timeout
119        supported.
120      TimeoutError: The expected event does not occur within time limit.
121    """
122    if timeout:
123      if timeout > MAX_TIMEOUT:
124        raise Error(
125            self._ad, 'Specified timeout %s is longer than max timeout %s.' %
126            (timeout, MAX_TIMEOUT))
127    try:
128      raw_event = self._callEventWaitAndGet(self._id, event_name, timeout)
129    except Exception as e:
130      if 'EventSnippetException: timeout.' in str(e):
131        raise TimeoutError(
132            self._ad, 'Timed out after waiting %ss for event "%s" triggered by'
133            ' %s (%s).' % (timeout, event_name, self._method_name, self._id))
134      raise
135    return snippet_event.from_dict(raw_event)
136
137  def waitForEvent(self, event_name, predicate, timeout=DEFAULT_TIMEOUT):
138    """Wait for an event of a specific name that satisfies the predicate.
139
140    This call will block until the expected event has been received or time
141    out.
142
143    The predicate function defines the condition the event is expected to
144    satisfy. It takes an event and returns True if the condition is
145    satisfied, False otherwise.
146
147    Note all events of the same name that are received but don't satisfy
148    the predicate will be discarded and not be available for further
149    consumption.
150
151    Args:
152      event_name: string, the name of the event to wait for.
153      predicate: function, a function that takes an event (dictionary) and
154        returns a bool.
155      timeout: float, default is 120s.
156
157    Returns:
158      dictionary, the event that satisfies the predicate if received.
159
160    Raises:
161      TimeoutError: raised if no event that satisfies the predicate is
162        received after timeout seconds.
163    """
164    deadline = time.perf_counter() + timeout
165    while time.perf_counter() <= deadline:
166      # Calculate the max timeout for the next event rpc call.
167      rpc_timeout = deadline - time.perf_counter()
168      if rpc_timeout < 0:
169        break
170      # A single RPC call cannot exceed MAX_TIMEOUT.
171      rpc_timeout = min(rpc_timeout, MAX_TIMEOUT)
172      try:
173        event = self.waitAndGet(event_name, rpc_timeout)
174      except TimeoutError:
175        # Ignoring TimeoutError since we need to throw one with a more
176        # specific message.
177        break
178      if predicate(event):
179        return event
180    raise TimeoutError(
181        self._ad,
182        'Timed out after %ss waiting for an "%s" event that satisfies the '
183        'predicate "%s".' % (timeout, event_name, predicate.__name__))
184
185  def getAll(self, event_name):
186    """Gets all the events of a certain name that have been received so
187    far. This is a non-blocking call.
188
189    Args:
190      callback_id: The id of the callback.
191      event_name: string, the name of the event to get.
192
193    Returns:
194      A list of SnippetEvent, each representing an event from the Java
195      side.
196    """
197    raw_events = self._callEventGetAll(self._id, event_name)
198    return [snippet_event.from_dict(msg) for msg in raw_events]
199