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