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 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 
     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.")
     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")
     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")
    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")
    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.")
    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.")
    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.")
    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.")
    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      */
    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      */
    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      */
    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      */
    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")
    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.")
    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.")
    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.")
    378     public void closeSl4aSession() {
    379         eventClearBuffer();
    380         postEvent("EventDispatcherShutdown", null);
    381     }
    382 
    383     /**
    384      * Shuts down the RPC server.
    385      */
    386     @Override
    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      */
    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      */
    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      */
    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 
    422         public BroadcastListener(EventFacade parent, boolean enqueue) {
    423             mParent = parent;
    424             mEnQueue = enqueue;
    425         }
    426 
    427         @Override
    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