/* | |
* Copyright (C) 2010 Google Inc. | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); you may not | |
* use this file except in compliance with the License. You may obtain a copy of | |
* the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
* License for the specific language governing permissions and limitations under | |
* the License. | |
*/ | |
package com.googlecode.android_scripting.facade; | |
import android.content.Context; | |
import android.content.Intent; | |
import android.content.IntentFilter; | |
import android.os.Bundle; | |
import com.google.common.collect.ArrayListMultimap; | |
import com.google.common.collect.Lists; | |
import com.google.common.collect.Multimap; | |
import com.google.common.collect.Multimaps; | |
import com.googlecode.android_scripting.Log; | |
import com.googlecode.android_scripting.event.Event; | |
import com.googlecode.android_scripting.future.FutureResult; | |
import com.googlecode.android_scripting.jsonrpc.JsonBuilder; | |
import com.googlecode.android_scripting.jsonrpc.RpcReceiver; | |
import com.googlecode.android_scripting.rpc.Rpc; | |
import com.googlecode.android_scripting.rpc.RpcDefault; | |
import com.googlecode.android_scripting.rpc.RpcDeprecated; | |
import com.googlecode.android_scripting.rpc.RpcName; | |
import com.googlecode.android_scripting.rpc.RpcOptional; | |
import com.googlecode.android_scripting.rpc.RpcParameter; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Queue; | |
import java.util.Set; | |
import java.util.concurrent.ConcurrentLinkedQueue; | |
import java.util.concurrent.CopyOnWriteArrayList; | |
import java.util.concurrent.TimeUnit; | |
import org.json.JSONException; | |
/** | |
* Manage the event queue. <br> | |
* <br> | |
* <b>Usage Notes:</b><br> | |
* EventFacade APIs interact with the Event Queue (a data buffer containing up to 1024 event | |
* entries).<br> | |
* Events are automatically entered into the Event Queue following API calls such as startSensing() | |
* and startLocating().<br> | |
* The Event Facade provides control over how events are entered into (and removed from) the Event | |
* Queue.<br> | |
* The Event Queue provides a useful means of recording background events (such as sensor data) when | |
* the phone is busy with foreground activities. | |
* | |
* @author Felix Arends (felix.arends@gmail.com) | |
* | |
*/ | |
public class EventFacade extends RpcReceiver { | |
/** | |
* The maximum length of the event queue. Old events will be discarded when this limit is | |
* exceeded. | |
*/ | |
private static final int MAX_QUEUE_SIZE = 1024; | |
private final Queue<Event> mEventQueue = new ConcurrentLinkedQueue<Event>(); | |
private final CopyOnWriteArrayList<EventObserver> mGlobalEventObservers = | |
new CopyOnWriteArrayList<EventObserver>(); | |
private final Multimap<String, EventObserver> mNamedEventObservers = Multimaps | |
.synchronizedListMultimap(ArrayListMultimap.<String, EventObserver> create()); | |
private EventServer mEventServer = null; | |
private final HashMap<String, BroadcastListener> mBroadcastListeners = | |
new HashMap<String, BroadcastListener>(); | |
private final Context mContext; | |
private boolean bEventServerRunning; | |
public EventFacade(FacadeManager manager) { | |
super(manager); | |
mContext = manager.getService().getApplicationContext(); | |
Log.v("Creating new EventFacade Instance()"); | |
bEventServerRunning = false; | |
} | |
/** | |
* Example (python): droid.eventClearBuffer() | |
*/ | |
@Rpc(description = "Clears all events from the event buffer.") | |
public void eventClearBuffer() { | |
mEventQueue.clear(); | |
} | |
/** | |
* Registers a listener for a new broadcast signal | |
*/ | |
@Rpc(description = "Registers a listener for a new broadcast signal") | |
public boolean eventRegisterForBroadcast( | |
@RpcParameter(name = "category") String category, | |
@RpcParameter(name = "enqueue", description = "Should this events be added to the event queue or only dispatched") @RpcDefault(value = "true") Boolean enqueue) { | |
if (mBroadcastListeners.containsKey(category)) { | |
return false; | |
} | |
BroadcastListener b = new BroadcastListener(this, enqueue.booleanValue()); | |
IntentFilter c = new IntentFilter(category); | |
mContext.registerReceiver(b, c); | |
mBroadcastListeners.put(category, b); | |
return true; | |
} | |
@Rpc(description = "Stop listening for a broadcast signal") | |
public void eventUnregisterForBroadcast(@RpcParameter(name = "category") String category) { | |
if (!mBroadcastListeners.containsKey(category)) { | |
return; | |
} | |
mContext.unregisterReceiver(mBroadcastListeners.get(category)); | |
mBroadcastListeners.remove(category); | |
} | |
@Rpc(description = "Lists all the broadcast signals we are listening for") | |
public Set<String> eventGetBrodcastCategories() { | |
return mBroadcastListeners.keySet(); | |
} | |
/** | |
* Actual data returned in the map will depend on the type of event. | |
* | |
* <pre> | |
* Example (python): | |
* import android, time | |
* droid = android.Android() | |
* droid.startSensing() | |
* time.sleep(1) | |
* droid.eventClearBuffer() | |
* time.sleep(1) | |
* e = eventPoll(1).result | |
* event_entry_number = 0 | |
* x = e[event_entry_ number]['data']['xforce'] | |
* </pre> | |
* | |
* e has the format:<br> | |
* [{u'data': {u'accuracy': 0, u'pitch': -0.48766891956329345, u'xmag': -5.6875, u'azimuth': | |
* 0.3312483489513397, u'zforce': 8.3492730000000002, u'yforce': 4.5628165999999997, u'time': | |
* 1297072704.813, u'ymag': -11.125, u'zmag': -42.375, u'roll': -0.059393649548292161, u'xforce': | |
* 0.42223078000000003}, u'name': u'sensors', u'time': 1297072704813000L}]<br> | |
* x has the string value of the x force data (0.42223078000000003) at the time of the event | |
* entry. </pre> | |
*/ | |
@Rpc(description = "Returns and removes the oldest n events (i.e. location or sensor update, etc.) from the event buffer.", returns = "A List of Maps of event properties.") | |
public List<Event> eventPoll( | |
@RpcParameter(name = "number_of_events") @RpcDefault("1") Integer number_of_events) { | |
List<Event> events = Lists.newArrayList(); | |
for (int i = 0; i < number_of_events; i++) { | |
Event event = mEventQueue.poll(); | |
if (event == null) { | |
break; | |
} | |
events.add(event); | |
} | |
return events; | |
} | |
@Rpc(description = "Blocks until an event with the supplied name occurs. Event is removed from the buffer if removeEvent is True.", | |
returns = "Map of event properties.") | |
public Event eventWaitFor( | |
@RpcParameter(name = "eventName") final String eventName, | |
@RpcParameter(name = "removeEvent") final Boolean removeEvent, | |
@RpcParameter(name = "timeout", description = "the maximum time to wait (in ms)") @RpcOptional Integer timeout) | |
throws InterruptedException { | |
Event result = null; | |
synchronized (mEventQueue) { // First check to make sure it isn't already there | |
for (Event event : mEventQueue) { | |
if (event.getName().equals(eventName)) { | |
result = event; | |
if(removeEvent) | |
mEventQueue.remove(event); | |
return result; | |
} | |
} | |
} | |
final FutureResult<Event> futureEvent = new FutureResult<Event>(); | |
addNamedEventObserver(eventName, new EventObserver() { | |
@Override | |
public void onEventReceived(Event event) { | |
if (event.getName().equals(eventName)) { | |
synchronized (futureEvent) { | |
if (!futureEvent.isDone()) { | |
futureEvent.set(event); | |
removeEventObserver(this); | |
} | |
if(removeEvent) | |
mEventQueue.remove(event); | |
} | |
} | |
} | |
}); | |
if (timeout != null) { | |
result = futureEvent.get(timeout, TimeUnit.MILLISECONDS); | |
} else { | |
result = futureEvent.get(); | |
} | |
return result; | |
} | |
@Rpc(description = "Blocks until an event occurs. The returned event is removed from the buffer.", returns = "Map of event properties.") | |
public Event eventWait( | |
@RpcParameter(name = "timeout", description = "the maximum time to wait") @RpcOptional Integer timeout) | |
throws InterruptedException { | |
Event result = null; | |
final FutureResult<Event> futureEvent = new FutureResult<Event>(); | |
synchronized (mEventQueue) { // Anything in queue? | |
if (mEventQueue.size() > 0) { | |
return mEventQueue.poll(); // return it. | |
} | |
} | |
EventObserver observer = new EventObserver() { | |
@Override | |
public void onEventReceived(Event event) { // set up observer for any events. | |
synchronized (futureEvent) { | |
if (!futureEvent.isDone()) { | |
futureEvent.set(event); | |
} | |
removeEventObserver(this); | |
mEventQueue.remove(event); | |
} | |
} | |
}; | |
addGlobalEventObserver(observer); | |
if (timeout != null) { | |
result = futureEvent.get(timeout, TimeUnit.MILLISECONDS); | |
if(result == null){ | |
result = new Event( "eventTimeout", null); | |
} | |
} else { | |
result = futureEvent.get(); | |
} | |
removeEventObserver(observer); // Make quite sure this goes away. | |
return result; | |
} | |
/** | |
* <pre> | |
* Example: | |
* import android | |
* from datetime import datetime | |
* droid = android.Android() | |
* t = datetime.now() | |
* droid.eventPost('Some Event', t) | |
* </pre> | |
*/ | |
@Rpc(description = "Post an event to the event queue.") | |
public void eventPost( | |
@RpcParameter(name = "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) { | |
postEvent(name, data, enqueue.booleanValue()); | |
} | |
/** | |
* Post an event and queue it | |
*/ | |
public void postEvent(String name, Object data) { | |
postEvent(name, data, true); | |
} | |
/** | |
* Posts an event with to the event queue. | |
*/ | |
public void postEvent(String name, Object data, boolean enqueue) { | |
Event event = new Event(name, data); | |
if (enqueue != false) { | |
synchronized (mEventQueue) { | |
while (mEventQueue.size() >= MAX_QUEUE_SIZE) { | |
mEventQueue.remove(); | |
} | |
mEventQueue.add(event); | |
} | |
Log.v(String.format("postEvent(%s)", name)); | |
} | |
synchronized (mNamedEventObservers) { | |
for (EventObserver observer : mNamedEventObservers.get(name)) { | |
observer.onEventReceived(event); | |
} | |
} | |
synchronized (mGlobalEventObservers) { | |
for (EventObserver observer : mGlobalEventObservers) { | |
observer.onEventReceived(event); | |
} | |
} | |
} | |
@RpcDeprecated(value = "eventPost", release = "r4") | |
@Rpc(description = "Post an event to the event queue.") | |
@RpcName(name = "postEvent") | |
public void rpcPostEvent(@RpcParameter(name = "name") String name, | |
@RpcParameter(name = "data") String data) { | |
postEvent(name, data); | |
} | |
@RpcDeprecated(value = "eventPoll", release = "r4") | |
@Rpc(description = "Returns and removes the oldest event (i.e. location or sensor update, etc.) from the event buffer.", returns = "Map of event properties.") | |
public Event receiveEvent() { | |
return mEventQueue.poll(); | |
} | |
@RpcDeprecated(value = "eventWaitFor", release = "r4") | |
@Rpc(description = "Blocks until an event with the supplied name occurs. Event is removed from the buffer if removeEvent is True.", | |
returns = "Map of event properties.") | |
public Event waitForEvent( | |
@RpcParameter(name = "eventName") final String eventName, | |
@RpcOptional final Boolean removeEvent, | |
@RpcParameter(name = "timeout", description = "the maximum time to wait") @RpcOptional Integer timeout) | |
throws InterruptedException { | |
return eventWaitFor(eventName, removeEvent, timeout); | |
} | |
@Rpc(description = "Opens up a socket where you can read for events posted") | |
public int startEventDispatcher( | |
@RpcParameter(name = "port", description = "Port to use") @RpcDefault("0") @RpcOptional() Integer port) { | |
if (mEventServer == null) { | |
if (port == null) { | |
port = 0; | |
} | |
mEventServer = new EventServer(port); | |
addGlobalEventObserver(mEventServer); | |
bEventServerRunning = true; | |
} | |
return mEventServer.getAddress().getPort(); | |
} | |
@Rpc(description = "Stops the event server, you can't read in the port anymore") | |
public void stopEventDispatcher() throws RuntimeException { | |
if (bEventServerRunning == true) { | |
if (mEventServer == null) { | |
throw new RuntimeException("Not running"); | |
} | |
bEventServerRunning = false; | |
mEventServer.shutdown(); | |
removeEventObserver(mEventServer); | |
mEventServer = null; | |
} | |
return; | |
} | |
@Override | |
public void shutdown() { | |
try { | |
stopEventDispatcher(); | |
} catch (Exception e) { | |
Log.e("Exception tearing down event dispatcher", e); | |
} | |
mGlobalEventObservers.clear(); | |
mEventQueue.clear(); | |
} | |
public void addNamedEventObserver(String eventName, EventObserver observer) { | |
mNamedEventObservers.put(eventName, observer); | |
} | |
public void addGlobalEventObserver(EventObserver observer) { | |
mGlobalEventObservers.add(observer); | |
} | |
public void removeEventObserver(EventObserver observer) { | |
mNamedEventObservers.removeAll(observer); | |
mGlobalEventObservers.remove(observer); | |
} | |
public interface EventObserver { | |
public void onEventReceived(Event event); | |
} | |
public class BroadcastListener extends android.content.BroadcastReceiver { | |
private EventFacade mParent; | |
private boolean mEnQueue; | |
public BroadcastListener(EventFacade parent, boolean enqueue) { | |
mParent = parent; | |
mEnQueue = enqueue; | |
} | |
@Override | |
public void onReceive(Context context, Intent intent) { | |
Bundle data; | |
if (intent.getExtras() != null) { | |
data = (Bundle) intent.getExtras().clone(); | |
} else { | |
data = new Bundle(); | |
} | |
data.putString("action", intent.getAction()); | |
try { | |
mParent.eventPost("sl4a", JsonBuilder.build(data).toString(), mEnQueue); | |
} catch (JSONException e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
} |