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