Home | History | Annotate | Download | only in facade
      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 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  */
     66 public 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