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