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