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
17package com.googlecode.android_scripting.facade;
18
19import java.util.HashMap;
20import java.util.List;
21import java.util.Queue;
22import java.util.Set;
23import java.util.concurrent.ConcurrentLinkedQueue;
24import java.util.concurrent.CopyOnWriteArrayList;
25import java.util.concurrent.TimeUnit;
26
27import org.json.JSONException;
28
29import android.content.Context;
30import android.content.Intent;
31import android.content.IntentFilter;
32import android.os.Bundle;
33
34import com.google.common.collect.ArrayListMultimap;
35import com.google.common.collect.Lists;
36import com.google.common.collect.Multimap;
37import com.google.common.collect.Multimaps;
38import com.googlecode.android_scripting.Log;
39import com.googlecode.android_scripting.event.Event;
40import com.googlecode.android_scripting.event.EventObserver;
41import com.googlecode.android_scripting.event.EventServer;
42import com.googlecode.android_scripting.future.FutureResult;
43import com.googlecode.android_scripting.jsonrpc.JsonBuilder;
44import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
45import com.googlecode.android_scripting.rpc.Rpc;
46import com.googlecode.android_scripting.rpc.RpcDefault;
47import com.googlecode.android_scripting.rpc.RpcDeprecated;
48import com.googlecode.android_scripting.rpc.RpcName;
49import com.googlecode.android_scripting.rpc.RpcOptional;
50import 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 */
66public 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