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