• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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                                 removeEventObserver(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         mNamedEventObservers.removeAll(observer);
415         mGlobalEventObservers.remove(observer);
416     }
417 
418     public class BroadcastListener extends android.content.BroadcastReceiver {
419         private EventFacade mParent;
420         private boolean mEnQueue;
421 
BroadcastListener(EventFacade parent, boolean enqueue)422         public BroadcastListener(EventFacade parent, boolean enqueue) {
423             mParent = parent;
424             mEnQueue = enqueue;
425         }
426 
427         @Override
onReceive(Context context, Intent intent)428         public void onReceive(Context context, Intent intent) {
429             Bundle data;
430             if (intent.getExtras() != null) {
431                 data = (Bundle) intent.getExtras().clone();
432             } else {
433                 data = new Bundle();
434             }
435             data.putString("action", intent.getAction());
436             try {
437                 mParent.eventPost("sl4a", JsonBuilder.build(data).toString(), mEnQueue);
438             } catch (JSONException e) {
439                 e.printStackTrace();
440             }
441         }
442 
443     }
444 }
445