com.googlecode.android_scripting.facade.EventFacade.java Source code

Java tutorial

Introduction

Here is the source code for com.googlecode.android_scripting.facade.EventFacade.java

Source

/*
 * 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.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;

    public EventFacade(FacadeManager manager) {
        super(manager);
        mContext = manager.getService().getApplicationContext();
    }

    /**
     * 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. The returned event is not removed from the buffer.", returns = "Map of event properties.")
    public Event eventWaitFor(@RpcParameter(name = "eventName") final String eventName,
            @RpcParameter(name = "timeout", description = "the maximum time to wait (in ms)") @RpcOptional Integer timeout)
            throws InterruptedException {
        synchronized (mEventQueue) { // First check to make sure it isn't already there
            for (Event event : mEventQueue) {
                if (event.getName().equals(eventName)) {
                    return event;
                }
            }
        }
        final FutureResult<Event> futureEvent = new FutureResult<Event>();
        addNamedEventObserver(eventName, new EventObserver() {
            public void onEventReceived(Event event) {
                if (event.getName().equals(eventName)) {
                    synchronized (futureEvent) {
                        if (!futureEvent.isDone()) {
                            futureEvent.set(event);
                            removeEventObserver(this);
                        }
                    }
                }
            }
        });
        if (timeout != null) {
            return futureEvent.get(timeout, TimeUnit.MILLISECONDS);
        } else {
            return futureEvent.get();
        }
    }

    @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() {
            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);
        } 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) {
            mEventQueue.add(event);
            if (mEventQueue.size() > MAX_QUEUE_SIZE) {
                mEventQueue.remove();
            }
        }
        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. The returned event is not removed from the buffer.", returns = "Map of event properties.")
    public Event waitForEvent(@RpcParameter(name = "eventName") final String eventName,
            @RpcParameter(name = "timeout", description = "the maximum time to wait") @RpcOptional Integer timeout)
            throws InterruptedException {
        return eventWaitFor(eventName, 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);
        }
        return mEventServer.getAddress().getPort();
    }

    @Rpc(description = "Stops the event server, you can't read in the port anymore")
    public void stopEventDispatcher() throws RuntimeException {
        if (mEventServer == null) {
            throw new RuntimeException("Not running");
        }
        mEventServer.shutdown();
        removeEventObserver(mEventServer);
        mEventServer = null;
        return;
    }

    @Override
    public void shutdown() {
        try {
            stopEventDispatcher();
        } catch (Exception err) {
        }
        // let others (like webviews) know we're going down
        postEvent("sl4a", "{\"shutdown\": \"event-facade\"}");
    }

    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();
            }
        }

    }
}