Home | History | Annotate | Download | only in facade
      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 (at) 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 
     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.")
     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")
    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")
    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")
    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.")
    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.")
    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.")
    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.")
    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      */
    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      */
    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")
    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.")
    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.")
    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")
    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.")
    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")
    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
    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 
    398     public void addNamedEventObserver(String eventName, EventObserver observer) {
    399         mNamedEventObservers.put(eventName, observer);
    400     }
    401 
    402     public void addGlobalEventObserver(EventObserver observer) {
    403         mGlobalEventObservers.add(observer);
    404     }
    405 
    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 
    415         public BroadcastListener(EventFacade parent, boolean enqueue) {
    416             mParent = parent;
    417             mEnQueue = enqueue;
    418         }
    419 
    420         @Override
    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