1 /* 2 * Copyright (C) 2016 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.googlecode.android_scripting.facade; 18 19 import java.util.HashMap; 20 import java.util.List; 21 import java.util.Queue; 22 import java.util.Set; 23 import java.util.concurrent.ConcurrentLinkedQueue; 24 import java.util.concurrent.CopyOnWriteArrayList; 25 import java.util.concurrent.TimeUnit; 26 27 import org.json.JSONException; 28 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.IntentFilter; 32 import android.os.Bundle; 33 34 import com.google.common.collect.ArrayListMultimap; 35 import com.google.common.collect.Lists; 36 import com.google.common.collect.Multimap; 37 import com.google.common.collect.Multimaps; 38 import com.googlecode.android_scripting.Log; 39 import com.googlecode.android_scripting.event.Event; 40 import com.googlecode.android_scripting.event.EventObserver; 41 import com.googlecode.android_scripting.event.EventServer; 42 import com.googlecode.android_scripting.future.FutureResult; 43 import com.googlecode.android_scripting.jsonrpc.JsonBuilder; 44 import com.googlecode.android_scripting.jsonrpc.RpcReceiver; 45 import com.googlecode.android_scripting.rpc.Rpc; 46 import com.googlecode.android_scripting.rpc.RpcDefault; 47 import com.googlecode.android_scripting.rpc.RpcDeprecated; 48 import com.googlecode.android_scripting.rpc.RpcName; 49 import com.googlecode.android_scripting.rpc.RpcOptional; 50 import com.googlecode.android_scripting.rpc.RpcParameter; 51 52 /** 53 * Manage the event queue. <br> 54 * <br> 55 * <b>Usage Notes:</b><br> 56 * EventFacade APIs interact with the Event Queue (a data buffer containing up to 1024 event 57 * entries).<br> 58 * Events are automatically entered into the Event Queue following API calls such as startSensing() 59 * and startLocating().<br> 60 * The Event Facade provides control over how events are entered into (and removed from) the Event 61 * Queue.<br> 62 * The Event Queue provides a useful means of recording background events (such as sensor data) when 63 * the phone is busy with foreground activities. 64 * 65 * @author Felix Arends (felix.arends@gmail.com) 66 */ 67 public class EventFacade extends RpcReceiver { 68 /** 69 * The maximum length of the event queue. Old events will be discarded when this limit is 70 * exceeded. 71 */ 72 private static final int MAX_QUEUE_SIZE = 1024; 73 private final Queue<Event> mEventQueue = new ConcurrentLinkedQueue<Event>(); 74 private final CopyOnWriteArrayList<EventObserver> mGlobalEventObservers = 75 new CopyOnWriteArrayList<EventObserver>(); 76 private final Multimap<String, EventObserver> mNamedEventObservers = Multimaps 77 .synchronizedListMultimap(ArrayListMultimap.<String, EventObserver> create()); 78 private EventServer mEventServer = null; 79 private final HashMap<String, BroadcastListener> mBroadcastListeners = 80 new HashMap<String, BroadcastListener>(); 81 private final Context mContext; 82 private boolean bEventServerRunning; 83 EventFacade(FacadeManager manager)84 public EventFacade(FacadeManager manager) { 85 super(manager); 86 mContext = manager.getService().getApplicationContext(); 87 Log.v("Creating new EventFacade Instance()"); 88 bEventServerRunning = false; 89 } 90 91 /** 92 * Example (python): droid.eventClearBuffer() 93 */ 94 @Rpc(description = "Clears all events from the event buffer.") eventClearBuffer()95 public void eventClearBuffer() { 96 mEventQueue.clear(); 97 } 98 99 /** 100 * Registers a listener for a new broadcast signal 101 */ 102 @Rpc(description = "Registers a listener for a new broadcast signal") eventRegisterForBroadcast( @pcParametername = "category") String category, @RpcParameter(name = "enqueue", description = "Should this events be added to the event queue or only dispatched") @RpcDefault(value = "true") Boolean enqueue)103 public boolean eventRegisterForBroadcast( 104 @RpcParameter(name = "category") String category, 105 @RpcParameter(name = "enqueue", 106 description = "Should this events be added to the event queue or only dispatched") @RpcDefault(value = "true") Boolean enqueue) { 107 if (mBroadcastListeners.containsKey(category)) { 108 return false; 109 } 110 111 BroadcastListener b = new BroadcastListener(this, enqueue.booleanValue()); 112 IntentFilter c = new IntentFilter(category); 113 mContext.registerReceiver(b, c); 114 mBroadcastListeners.put(category, b); 115 116 return true; 117 } 118 119 @Rpc(description = "Stop listening for a broadcast signal") eventUnregisterForBroadcast( @pcParametername = "category") String category)120 public void eventUnregisterForBroadcast( 121 @RpcParameter(name = "category") String category) { 122 if (!mBroadcastListeners.containsKey(category)) { 123 return; 124 } 125 126 mContext.unregisterReceiver(mBroadcastListeners.get(category)); 127 mBroadcastListeners.remove(category); 128 } 129 130 @Rpc(description = "Lists all the broadcast signals we are listening for") eventGetBrodcastCategories()131 public Set<String> eventGetBrodcastCategories() { 132 return mBroadcastListeners.keySet(); 133 } 134 135 /** 136 * Actual data returned in the map will depend on the type of event. 137 * 138 * <pre> 139 * Example (python): 140 * import android, time 141 * droid = android.Android() 142 * droid.startSensing() 143 * time.sleep(1) 144 * droid.eventClearBuffer() 145 * time.sleep(1) 146 * e = eventPoll(1).result 147 * event_entry_number = 0 148 * x = e[event_entry_ number]['data']['xforce'] 149 * </pre> 150 * 151 * e has the format:<br> 152 * [{u'data': {u'accuracy': 0, u'pitch': -0.48766891956329345, u'xmag': -5.6875, u'azimuth': 153 * 0.3312483489513397, u'zforce': 8.3492730000000002, u'yforce': 4.5628165999999997, u'time': 154 * 1297072704.813, u'ymag': -11.125, u'zmag': -42.375, u'roll': -0.059393649548292161, 155 * u'xforce': 0.42223078000000003}, u'name': u'sensors', u'time': 1297072704813000L}]<br> 156 * x has the string value of the x force data (0.42223078000000003) at the time of the event 157 * entry. </pre> 158 */ 159 160 @Rpc(description = "Returns and removes the oldest n events (i.e. location or sensor update, etc.) from the event buffer.", 161 returns = "A List of Maps of event properties.") eventPoll( @pcParametername = "number_of_events") @pcDefault"1") Integer number_of_events)162 public List<Event> eventPoll( 163 @RpcParameter(name = "number_of_events") @RpcDefault("1") Integer number_of_events) { 164 List<Event> events = Lists.newArrayList(); 165 for (int i = 0; i < number_of_events; i++) { 166 Event event = mEventQueue.poll(); 167 if (event == null) { 168 break; 169 } 170 events.add(event); 171 } 172 return events; 173 } 174 175 @Rpc(description = "Blocks until an event with the supplied name occurs. Event is removed from the buffer if removeEvent is True.", 176 returns = "Map of event properties.") eventWaitFor( @pcParametername = "eventName") final String eventName, @RpcParameter(name = "removeEvent") final Boolean removeEvent, @RpcParameter(name = "timeout", description = "the maximum time to wait (in ms)") @RpcOptional Integer timeout)177 public Event eventWaitFor( 178 @RpcParameter(name = "eventName") 179 final String eventName, 180 @RpcParameter(name = "removeEvent") 181 final Boolean removeEvent, 182 @RpcParameter(name = "timeout", description = "the maximum time to wait (in ms)") @RpcOptional Integer timeout) 183 throws InterruptedException { 184 Event result = null; 185 final FutureResult<Event> futureEvent; 186 synchronized (mEventQueue) { // First check to make sure it isn't already there 187 for (Event event : mEventQueue) { 188 if (event.getName().equals(eventName)) { 189 result = event; 190 if (removeEvent) 191 mEventQueue.remove(event); 192 return result; 193 } 194 } 195 futureEvent = new FutureResult<Event>(); 196 addNamedEventObserver(eventName, new EventObserver() { 197 @Override 198 public void onEventReceived(Event event) { 199 if (event.getName().equals(eventName)) { 200 synchronized (futureEvent) { 201 if (!futureEvent.isDone()) { 202 futureEvent.set(event); 203 // TODO(navtej) Remove log. 204 Log.v(String.format("Removeing observer (%s) got event (%s)", 205 this, 206 event)); 207 removeEventObserver(this); 208 } 209 if (removeEvent) 210 mEventQueue.remove(event); 211 } 212 } 213 } 214 }); 215 } 216 if (futureEvent != null) { 217 if (timeout != null) { 218 result = futureEvent.get(timeout, TimeUnit.MILLISECONDS); 219 } else { 220 result = futureEvent.get(); 221 } 222 } 223 return result; 224 } 225 226 @Rpc(description = "Blocks until an event occurs. The returned event is removed from the buffer.", 227 returns = "Map of event properties.") eventWait( @pcParametername = "timeout", description = "the maximum time to wait") @pcOptional Integer timeout)228 public Event eventWait( 229 @RpcParameter(name = "timeout", description = "the maximum time to wait") @RpcOptional Integer timeout) 230 throws InterruptedException { 231 Event result = null; 232 final FutureResult<Event> futureEvent = new FutureResult<Event>(); 233 EventObserver observer; 234 synchronized (mEventQueue) { // Anything in queue? 235 if (mEventQueue.size() > 0) { 236 return mEventQueue.poll(); // return it. 237 } 238 observer = new EventObserver() { 239 @Override 240 public void onEventReceived(Event event) { // set up observer for any events. 241 synchronized (futureEvent) { 242 if (!futureEvent.isDone()) { 243 futureEvent.set(event); 244 // TODO(navtej) Remove log. 245 Log.v(String.format("onEventReceived for event (%s)", event)); 246 } 247 } 248 } 249 }; 250 addGlobalEventObserver(observer); 251 } 252 if (timeout != null) { 253 result = futureEvent.get(timeout, TimeUnit.MILLISECONDS); 254 } else { 255 result = futureEvent.get(); 256 } 257 if (result != null) { 258 mEventQueue.remove(result); 259 } 260 // TODO(navtej) Remove log. 261 Log.v(String.format("Removeing observer (%s) got event (%s)", observer, result)); 262 if (observer != null) { 263 removeEventObserver(observer); // Make quite sure this goes away. 264 } 265 return result; 266 } 267 268 /** 269 * <pre> 270 * Example: 271 * import android 272 * from datetime import datetime 273 * droid = android.Android() 274 * t = datetime.now() 275 * droid.eventPost('Some Event', t) 276 * </pre> 277 */ 278 @Rpc(description = "Post an event to the event queue.") eventPost( @pcParametername = "name", description = "Name of event") String name, @RpcParameter(name = "data", description = "Data contained in event.") String data, @RpcParameter(name = "enqueue", description = "Set to False if you don't want your events to be added to the event queue, just dispatched.") @RpcOptional @RpcDefault("false") Boolean enqueue)279 public void eventPost( 280 @RpcParameter(name = "name", description = "Name of event") String name, 281 @RpcParameter(name = "data", description = "Data contained in event.") String data, 282 @RpcParameter(name = "enqueue", 283 description = "Set to False if you don't want your events to be added to the event queue, just dispatched.") @RpcOptional @RpcDefault("false") Boolean enqueue) { 284 postEvent(name, data, enqueue.booleanValue()); 285 } 286 287 /** 288 * Post an event and queue it 289 */ postEvent(String name, Object data)290 public void postEvent(String name, Object data) { 291 postEvent(name, data, true); 292 } 293 294 /** 295 * Posts an event with to the event queue. 296 */ postEvent(String name, Object data, boolean enqueue)297 public void postEvent(String name, Object data, boolean enqueue) { 298 Event event = new Event(name, data); 299 if (enqueue != false) { 300 synchronized (mEventQueue) { 301 while (mEventQueue.size() >= MAX_QUEUE_SIZE) { 302 mEventQueue.remove(); 303 } 304 mEventQueue.add(event); 305 } 306 Log.v(String.format("postEvent(%s)", name)); 307 } 308 synchronized (mNamedEventObservers) { 309 for (EventObserver observer : mNamedEventObservers.get(name)) { 310 observer.onEventReceived(event); 311 } 312 } 313 synchronized (mGlobalEventObservers) { 314 // TODO(navtej) Remove log. 315 Log.v(String.format("mGlobalEventObservers size (%s)", mGlobalEventObservers.size())); 316 for (EventObserver observer : mGlobalEventObservers) { 317 observer.onEventReceived(event); 318 } 319 } 320 } 321 322 @RpcDeprecated(value = "eventPost", release = "r4") 323 @Rpc(description = "Post an event to the event queue.") 324 @RpcName(name = "postEvent") rpcPostEvent( @pcParametername = "name") String name, @RpcParameter(name = "data") String data)325 public void rpcPostEvent( 326 @RpcParameter(name = "name") String name, 327 @RpcParameter(name = "data") String data) { 328 postEvent(name, data); 329 } 330 331 @RpcDeprecated(value = "eventPoll", release = "r4") 332 @Rpc(description = "Returns and removes the oldest event (i.e. location or sensor update, etc.) from the event buffer.", 333 returns = "Map of event properties.") receiveEvent()334 public Event receiveEvent() { 335 return mEventQueue.poll(); 336 } 337 338 @RpcDeprecated(value = "eventWaitFor", release = "r4") 339 @Rpc(description = "Blocks until an event with the supplied name occurs. Event is removed from the buffer if removeEvent is True.", 340 returns = "Map of event properties.") waitForEvent( @pcParametername = "eventName") final String eventName, @RpcOptional final Boolean removeEvent, @RpcParameter(name = "timeout", description = "the maximum time to wait") @RpcOptional Integer timeout)341 public Event waitForEvent( 342 @RpcParameter(name = "eventName") 343 final String eventName, 344 @RpcOptional 345 final Boolean removeEvent, 346 @RpcParameter(name = "timeout", description = "the maximum time to wait") @RpcOptional Integer timeout) 347 throws InterruptedException { 348 return eventWaitFor(eventName, removeEvent, timeout); 349 } 350 351 @Rpc(description = "Opens up a socket where you can read for events posted") startEventDispatcher( @pcParametername = "port", description = "Port to use") @pcDefault"0") @pcOptional) Integer port)352 public int startEventDispatcher( 353 @RpcParameter(name = "port", description = "Port to use") @RpcDefault("0") @RpcOptional() Integer port) { 354 if (mEventServer == null) { 355 if (port == null) { 356 port = 0; 357 } 358 mEventServer = new EventServer(port); 359 addGlobalEventObserver(mEventServer); 360 bEventServerRunning = true; 361 } 362 return mEventServer.getAddress().getPort(); 363 } 364 365 @Rpc(description = "sl4a session is shutting down, send terminate event to client.") closeSl4aSession()366 public void closeSl4aSession() { 367 eventClearBuffer(); 368 postEvent("EventDispatcherShutdown", null); 369 } 370 371 @Rpc(description = "Stops the event server, you can't read in the port anymore") stopEventDispatcher()372 public void stopEventDispatcher() throws RuntimeException { 373 if (bEventServerRunning == true) { 374 if (mEventServer == null) { 375 throw new RuntimeException("Not running"); 376 } 377 bEventServerRunning = false; 378 mEventServer.shutdown(); 379 Log.v(String.format("stopEventDispatcher (%s)", mEventServer)); 380 removeEventObserver(mEventServer); 381 mEventServer = null; 382 } 383 return; 384 } 385 386 @Override shutdown()387 public void shutdown() { 388 389 try { 390 stopEventDispatcher(); 391 } catch (Exception e) { 392 Log.e("Exception tearing down event dispatcher", e); 393 } 394 mGlobalEventObservers.clear(); 395 mEventQueue.clear(); 396 } 397 addNamedEventObserver(String eventName, EventObserver observer)398 public void addNamedEventObserver(String eventName, EventObserver observer) { 399 mNamedEventObservers.put(eventName, observer); 400 } 401 addGlobalEventObserver(EventObserver observer)402 public void addGlobalEventObserver(EventObserver observer) { 403 mGlobalEventObservers.add(observer); 404 } 405 removeEventObserver(EventObserver observer)406 public void removeEventObserver(EventObserver observer) { 407 mNamedEventObservers.removeAll(observer); 408 mGlobalEventObservers.remove(observer); 409 } 410 411 public class BroadcastListener extends android.content.BroadcastReceiver { 412 private EventFacade mParent; 413 private boolean mEnQueue; 414 BroadcastListener(EventFacade parent, boolean enqueue)415 public BroadcastListener(EventFacade parent, boolean enqueue) { 416 mParent = parent; 417 mEnQueue = enqueue; 418 } 419 420 @Override onReceive(Context context, Intent intent)421 public void onReceive(Context context, Intent intent) { 422 Bundle data; 423 if (intent.getExtras() != null) { 424 data = (Bundle) intent.getExtras().clone(); 425 } else { 426 data = new Bundle(); 427 } 428 data.putString("action", intent.getAction()); 429 try { 430 mParent.eventPost("sl4a", JsonBuilder.build(data).toString(), mEnQueue); 431 } catch (JSONException e) { 432 e.printStackTrace(); 433 } 434 } 435 436 } 437 } 438