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