edu.mit.media.funf.FunfManager.java Source code

Java tutorial

Introduction

Here is the source code for edu.mit.media.funf.FunfManager.java

Source

/**
 * 
 * Funf: Open Sensing Framework
 * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland.
 * Acknowledgments: Alan Gardner
 * Contact: nadav@media.mit.edu
 * 
 * This file is part of Funf.
 * 
 * Funf is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 * 
 * Funf is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with Funf. If not, see <http://www.gnu.org/licenses/>.
 * 
 */
package edu.mit.media.funf;

import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.TypeAdapterFactory;

import edu.mit.media.funf.Schedule.BasicSchedule;
import edu.mit.media.funf.Schedule.DefaultSchedule;
import edu.mit.media.funf.action.Action;
import edu.mit.media.funf.config.ConfigUpdater;
import edu.mit.media.funf.config.ConfigurableTypeAdapterFactory;
import edu.mit.media.funf.config.ContextInjectorTypeAdapaterFactory;
import edu.mit.media.funf.config.DefaultRuntimeTypeAdapterFactory;
import edu.mit.media.funf.config.DefaultScheduleSerializer;
import edu.mit.media.funf.config.HttpConfigUpdater;
import edu.mit.media.funf.config.ListenerInjectorTypeAdapterFactory;
import edu.mit.media.funf.config.SingletonTypeAdapterFactory;
import edu.mit.media.funf.datasource.Startable;
import edu.mit.media.funf.datasource.StartableDataSource;
import edu.mit.media.funf.pipeline.Pipeline;
import edu.mit.media.funf.pipeline.PipelineFactory;
import edu.mit.media.funf.probe.Probe;
import edu.mit.media.funf.probe.Probe.DataListener;
import edu.mit.media.funf.storage.DefaultArchive;
import edu.mit.media.funf.storage.FileArchive;
import edu.mit.media.funf.storage.HttpArchive;
import edu.mit.media.funf.storage.RemoteFileArchive;
import edu.mit.media.funf.util.LogUtil;
import edu.mit.media.funf.util.StringUtil;

public class FunfManager extends Service {

    public static final String ACTION_KEEP_ALIVE = "funf.keepalive", ACTION_INTERNAL = "funf.internal";

    private static final String PIPELINE_TYPE = "funf/pipeline", ALARM_TYPE = "funf/alarm";

    private static final String DISABLED_PIPELINE_LIST = "__DISABLED__";

    private Handler handler;
    private SharedPreferences prefs;
    private Map<String, Pipeline> pipelines;
    private Map<String, Pipeline> disabledPipelines;
    private Set<String> disabledPipelineNames;

    @Override
    public void onCreate() {
        super.onCreate();
        this.handler = new Handler();
        getGson(); // Sets gson
        this.prefs = getSharedPreferences(getClass().getName(), MODE_PRIVATE);
        this.pipelines = new HashMap<String, Pipeline>();
        this.disabledPipelines = new HashMap<String, Pipeline>();
        this.disabledPipelineNames = new HashSet<String>(
                Arrays.asList(prefs.getString(DISABLED_PIPELINE_LIST, "").split(",")));
        this.disabledPipelineNames.remove(""); // Remove the empty name, if no disabled pipelines exist
        reload();
    }

    public void reload() {
        if (Looper.myLooper() != Looper.getMainLooper()) {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    reload();
                }
            });
            return;
        }
        Set<String> pipelineNames = new HashSet<String>();
        pipelineNames.addAll(prefs.getAll().keySet());
        pipelineNames.remove(DISABLED_PIPELINE_LIST);
        Bundle metadata = getMetadata();
        pipelineNames.addAll(metadata.keySet());
        for (String pipelineName : pipelineNames) {
            reload(pipelineName);
        }
    }

    public void reload(final String name) {
        if (Looper.myLooper() != Looper.getMainLooper()) {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    reload(name);
                }
            });
            return;
        }
        String pipelineConfig = null;
        Bundle metadata = getMetadata();
        if (prefs.contains(name)) {
            pipelineConfig = prefs.getString(name, null);
        } else if (metadata.containsKey(name)) {
            pipelineConfig = metadata.getString(name);
        }
        if (disabledPipelineNames.contains(name)) {
            // Disabled, so don't load any config
            Pipeline disabledPipeline = gson.fromJson(pipelineConfig, Pipeline.class);
            disabledPipelines.put(name, disabledPipeline);
            pipelineConfig = null;
        }
        if (pipelineConfig == null) {
            unregisterPipeline(name);
        } else {
            Pipeline newPipeline = gson.fromJson(pipelineConfig, Pipeline.class);
            registerPipeline(name, newPipeline); // Will unregister previous before running
        }
    }

    public JsonObject getPipelineConfig(String name) {
        String configString = prefs.getString(name, null);
        Bundle metadata = getMetadata();
        if (configString == null && metadata.containsKey(name)) {
            configString = metadata.getString(name);
        }
        return configString == null ? null : new JsonParser().parse(configString).getAsJsonObject();
    }

    public boolean save(String name, JsonObject config) {
        try {
            // Check if this is a valid pipeline before saving
            Pipeline pipeline = getGson().fromJson(config, Pipeline.class);
            return prefs.edit().putString(name, config.toString()).commit();
        } catch (Exception e) {
            Log.e(LogUtil.TAG, "Unable to save config: " + config.toString());
            return false;
        }
    }

    public boolean saveAndReload(String name, JsonObject config) {
        boolean success = save(name, config);
        if (success) {
            reload(name);
        }
        return success;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        // TODO: call onDestroy on all pipelines
        for (Pipeline pipeline : pipelines.values()) {
            pipeline.onDestroy();
        }

        // TODO: save outstanding requests
        // TODO: remove all remaining Alarms

        // TODO: make sure to destroy all probes
        for (Object probeObject : getProbeFactory().getCached()) {
            //String componentString = JsonUtils.immutable(gson.toJsonTree(probeObject)).toString();
            //cancelProbe(componentString);
            ((Probe) probeObject).destroy();
        }
        getProbeFactory().clearCache();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        String action = intent.getAction();
        if (action == null || ACTION_KEEP_ALIVE.equals(action)) {
            // Does nothing, but wakes up FunfManager
        } else if (ACTION_INTERNAL.equals(action)) {
            String type = intent.getType();
            Uri componentUri = intent.getData();
            if (PIPELINE_TYPE.equals(type)) {
                // Handle pipeline action
                String pipelineName = getComponentName(componentUri);
                String pipelineAction = getAction(componentUri);
                Pipeline pipeline = pipelines.get(pipelineName);
                if (pipeline != null) {
                    pipeline.onRun(pipelineAction, null);
                }
            } else if (ALARM_TYPE.equals(type)) {
                // Handle registered alarms
                String probeConfig = getComponentName(componentUri);
                final Probe probe = getGson().fromJson(probeConfig, Probe.class);
                if (probe instanceof Runnable) {
                    handler.post((Runnable) probe);
                }
            }

        }
        return Service.START_FLAG_RETRY; // TODO: may want the last intent always redelivered to make sure system starts up
    }

    private Bundle getMetadata() {
        try {
            Bundle metadata = getPackageManager().getServiceInfo(new ComponentName(this, this.getClass()),
                    PackageManager.GET_META_DATA).metaData;
            return metadata == null ? new Bundle() : metadata;
        } catch (NameNotFoundException e) {
            throw new RuntimeException("Unable to get metadata for the FunfManager service.");
        }
    }

    /**
     * Get a gson builder with the probe factory built in
     * @return
     */
    public GsonBuilder getGsonBuilder() {
        return getGsonBuilder(this);
    }

    public static class ConfigurableRuntimeTypeAdapterFactory<E> extends DefaultRuntimeTypeAdapterFactory<E> {

        public ConfigurableRuntimeTypeAdapterFactory(Context context, Class<E> baseClass,
                Class<? extends E> defaultClass) {
            super(context, baseClass, defaultClass,
                    new ContextInjectorTypeAdapaterFactory(context, new ConfigurableTypeAdapterFactory()));
        }

    }

    /**
     * Get a gson builder with the probe factory built in
     * @return
     */
    public static GsonBuilder getGsonBuilder(Context context) {
        return new GsonBuilder().registerTypeAdapterFactory(getProbeFactory(context))
                .registerTypeAdapterFactory(getActionFactory(context))
                .registerTypeAdapterFactory(getPipelineFactory(context))
                .registerTypeAdapterFactory(getDataSourceFactory(context))
                .registerTypeAdapterFactory(new ConfigurableRuntimeTypeAdapterFactory<Schedule>(context,
                        Schedule.class, BasicSchedule.class))
                .registerTypeAdapterFactory(new ConfigurableRuntimeTypeAdapterFactory<ConfigUpdater>(context,
                        ConfigUpdater.class, HttpConfigUpdater.class))
                .registerTypeAdapterFactory(new ConfigurableRuntimeTypeAdapterFactory<FileArchive>(context,
                        FileArchive.class, DefaultArchive.class))
                .registerTypeAdapterFactory(new ConfigurableRuntimeTypeAdapterFactory<RemoteFileArchive>(context,
                        RemoteFileArchive.class, HttpArchive.class))
                .registerTypeAdapterFactory(
                        new ConfigurableRuntimeTypeAdapterFactory<DataListener>(context, DataListener.class, null))
                .registerTypeAdapter(DefaultSchedule.class, new DefaultScheduleSerializer())
                .registerTypeAdapter(Class.class, new JsonSerializer<Class<?>>() {

                    @Override
                    public JsonElement serialize(Class<?> src, Type typeOfSrc, JsonSerializationContext context) {
                        return src == null ? JsonNull.INSTANCE : new JsonPrimitive(src.getName());
                    }
                });
    }

    private Gson gson;

    /**
     * Get a Gson instance which includes the SingletonProbeFactory
     * @return
     */
    public Gson getGson() {
        if (gson == null) {
            gson = getGsonBuilder().create();
        }
        return gson;
    }

    public TypeAdapterFactory getPipelineFactory() {
        return getPipelineFactory(this);
    }

    private static PipelineFactory PIPELINE_FACTORY;

    public static PipelineFactory getPipelineFactory(Context context) {
        if (PIPELINE_FACTORY == null) {
            PIPELINE_FACTORY = new PipelineFactory(context);
        }
        return PIPELINE_FACTORY;
    }

    public SingletonTypeAdapterFactory getProbeFactory() {
        return getProbeFactory(this);
    }

    private static SingletonTypeAdapterFactory PROBE_FACTORY;

    public static SingletonTypeAdapterFactory getProbeFactory(Context context) {
        if (PROBE_FACTORY == null) {
            PROBE_FACTORY = new SingletonTypeAdapterFactory(
                    new DefaultRuntimeTypeAdapterFactory<Probe>(context, Probe.class, null,
                            new ContextInjectorTypeAdapaterFactory(context, new ConfigurableTypeAdapterFactory())));
        }
        return PROBE_FACTORY;
    }

    public DefaultRuntimeTypeAdapterFactory<Action> getActionFactory() {
        return getActionFactory(this);
    }

    private static DefaultRuntimeTypeAdapterFactory<Action> ACTION_FACTORY;

    public static DefaultRuntimeTypeAdapterFactory<Action> getActionFactory(Context context) {
        if (ACTION_FACTORY == null) {
            ACTION_FACTORY = new DefaultRuntimeTypeAdapterFactory<Action>(context, Action.class, null,
                    new ContextInjectorTypeAdapaterFactory(context, new ConfigurableTypeAdapterFactory()));
        }
        return ACTION_FACTORY;
    }

    public ListenerInjectorTypeAdapterFactory getDataSourceFactory() {
        return getDataSourceFactory(this);
    }

    private static ListenerInjectorTypeAdapterFactory DATASOURCE_FACTORY;

    public static ListenerInjectorTypeAdapterFactory getDataSourceFactory(Context context) {
        if (DATASOURCE_FACTORY == null) {
            DATASOURCE_FACTORY = new ListenerInjectorTypeAdapterFactory(
                    new DefaultRuntimeTypeAdapterFactory<Startable>(context, Startable.class, null,
                            new ContextInjectorTypeAdapaterFactory(context, new ConfigurableTypeAdapterFactory())));
        }
        return DATASOURCE_FACTORY;
    }

    public void registerPipeline(String name, Pipeline pipeline) {
        synchronized (pipelines) {
            Log.d(LogUtil.TAG, "Registering pipeline: " + name);
            unregisterPipeline(name);
            pipelines.put(name, pipeline);
            pipeline.onCreate(this);
        }
    }

    public Pipeline getRegisteredPipeline(String name) {
        Pipeline p = pipelines.get(name);
        if (p == null) {
            p = disabledPipelines.get(name);
        }
        return p;
    }

    public void unregisterPipeline(String name) {
        synchronized (pipelines) {
            Pipeline existingPipeline = pipelines.remove(name);
            if (existingPipeline != null) {
                existingPipeline.onDestroy();
            }
        }
    }

    public void enablePipeline(String name) {
        boolean previouslyDisabled = disabledPipelineNames.remove(name);
        if (previouslyDisabled) {
            prefs.edit().putString(DISABLED_PIPELINE_LIST, StringUtil.join(disabledPipelineNames, ",")).commit();
            reload(name);
        }
    }

    public boolean isEnabled(String name) {
        return this.pipelines.containsKey(name) && !disabledPipelineNames.contains(name);
    }

    public void disablePipeline(String name) {
        boolean previouslyEnabled = disabledPipelineNames.add(name);
        if (previouslyEnabled) {
            prefs.edit().putString(DISABLED_PIPELINE_LIST, StringUtil.join(disabledPipelineNames, ",")).commit();
            reload(name);
        }
    }

    private String getPipelineName(Pipeline pipeline) {
        for (Map.Entry<String, Pipeline> entry : pipelines.entrySet()) {
            if (entry.getValue() == pipeline) {
                return entry.getKey();
            }
        }
        return null;
    }

    public class LocalBinder extends Binder {
        public FunfManager getManager() {
            return FunfManager.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return new LocalBinder();
    }

    public static void registerAlarm(Context context, String probeConfig, Long start, Long interval,
            boolean exact) {
        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

        Intent intent = getFunfIntent(context, ALARM_TYPE, probeConfig, "");
        PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent,
                PendingIntent.FLAG_UPDATE_CURRENT);

        if (start == null)
            start = 0L;

        if (interval == null || interval <= 0) {
            alarmManager.set(AlarmManager.RTC_WAKEUP, start, pendingIntent);
        } else {
            if (exact) {
                alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, start, interval, pendingIntent);
            } else {
                alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, start, interval, pendingIntent);
            }
        }
    }

    public static void unregisterAlarm(Context context, String probeConfig) {
        Intent intent = getFunfIntent(context, ALARM_TYPE, probeConfig, "");
        PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_NO_CREATE);
        if (pendingIntent != null) {
            pendingIntent.cancel();
        }
    }

    /////////////////////////////////////////////
    // Reserve action for later inter-funf communication
    // Use type to differentiate between probe/pipeline
    // funf:<componenent_name>#<action>

    private static final String FUNF_SCHEME = "funf";

    // TODO: should these public?  May be confusing for people just using the library
    private static Uri getComponentUri(String component, String action) {
        return new Uri.Builder().scheme(FUNF_SCHEME).path(component) // Automatically prepends slash
                .fragment(action).build();
    }

    private static String getComponentName(Uri componentUri) {
        return componentUri.getPath().substring(1); // Remove automatically prepended slash from beginning
    }

    private static String getAction(Uri componentUri) {
        return componentUri.getFragment();
    }

    private static Intent getFunfIntent(Context context, String type, String component, String action) {
        return getFunfIntent(context, type, getComponentUri(component, action));
    }

    private static Intent getFunfIntent(Context context, String type, Uri componentUri) {
        Intent intent = new Intent();
        intent.setClass(context, FunfManager.class);
        intent.setPackage(context.getPackageName());
        intent.setAction(ACTION_INTERNAL);
        intent.setDataAndType(componentUri, type);
        return intent;
    }
}