com.google.appinventor.components.runtime.ProbeBase.java Source code

Java tutorial

Introduction

Here is the source code for com.google.appinventor.components.runtime.ProbeBase.java

Source

// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2009-2011 Google, All Rights reserved
// Copyright 2011-2012 MIT, All rights reserved
// Released under the MIT License https://raw.github.com/mit-cml/app-inventor/master/mitlicense.txt

package com.google.appinventor.components.runtime;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectOutputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;

import android.R;
import android.accounts.NetworkErrorException;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;

import com.google.appinventor.components.annotations.DesignerProperty;
import com.google.appinventor.components.annotations.PropertyCategory;
import com.google.appinventor.components.annotations.SimpleFunction;
import com.google.appinventor.components.annotations.SimpleObject;
import com.google.appinventor.components.annotations.SimpleProperty;
import com.google.appinventor.components.annotations.UsesLibraries;
import com.google.appinventor.components.annotations.UsesPermissions;
import com.google.appinventor.components.common.PropertyTypeConstants;
import com.google.appinventor.components.runtime.util.SensorDbUtil;

//import com.google.appinventor.server.flags.Flag;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

import com.google.gson.JsonPrimitive;

import edu.mit.media.funf.FunfManager;

import edu.mit.media.funf.config.RuntimeTypeAdapterFactory;
import edu.mit.media.funf.json.IJsonObject;
import edu.mit.media.funf.probe.builtin.ProbeKeys.BaseProbeKeys;
import edu.mit.media.funf.storage.DatabaseService;
import edu.mit.media.funf.storage.NameValueDatabaseService;

import edu.mit.media.funf.time.DecimalTimeUnit;

@SimpleObject
@UsesPermissions(permissionNames = "android.permission.WRITE_EXTERNAL_STORAGE, "
        + "android.permission.ACCESS_NETWORK_STATE, " + "android.permission.WAKE_LOCK, "
        + "android.permission.READ_LOGS, " + "android.permission.VIBRATE, " + "android.permission.INTERNET")
@UsesLibraries(libraries = "funf.jar")
public abstract class ProbeBase extends AndroidNonvisibleComponent implements
        //SensorComponent, OnDestroyListener, Pipeline{
        SensorComponent, OnDestroyListener {
    protected boolean enabled = false; // run once
    protected boolean enabledSchedule = false; // run periodically
    boolean privacySafe = false; // by default sensitive values clear text

    private final String TAG = "ProbeBase";
    /*
     * Binding to FunfMananger service
     */

    protected FunfManager mBoundFunfManager = null;
    protected boolean mIsBound = false;
    //protected List<JsonElement> dataRequests = new ArrayList<JsonElement>();

    protected Activity mainUIThreadActivity;
    protected Gson gson;

    protected int interval;
    protected int duration;

    /*
     * Notification
     */
    private Notification notification;
    private PendingIntent mContentIntent;
    private NotificationManager mNM;
    private static final int PROBE_NOTIFICATION_MIN_ID = 8888;
    private static int probeCounter = 0;
    private final int PROBE_NOTIFICATION_ID;

    public static final String PASSWORD_KEY = "PASSWORD";

    // save to db
    protected boolean enabledSaveToDB = false; //local for each probe
    private String exportPath;
    private String exportFormat;

    private Calendar calendar = Calendar.getInstance(Locale.getDefault());
    private BigDecimal localOffsetSeconds = BigDecimal
            .valueOf(calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET), DecimalTimeUnit.MILLI);

    // try local binding to FunfManager
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            // This is called when the connection with the service has been
            // established, giving us the service object we can use to
            // interact with the service. Because we have bound to a explicit
            // service that we know is running in our own process, we can
            // cast its IBinder to a concrete class and directly access it.
            mBoundFunfManager = ((FunfManager.LocalBinder) service).getManager();

            //      registerSelfToFunfManager(); 

            Log.i(TAG, "Bound to FunfManager");

        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            // Because it is running in our same process, we should never
            // see this happen.
            mBoundFunfManager = null;

            Log.i(TAG, "Unbind FunfManager");

        }
    };

    private static int getNotificationID() {
        probeCounter += 1;
        return PROBE_NOTIFICATION_MIN_ID + probeCounter;

    }

    protected ProbeBase(ComponentContainer container) {
        super(container.$form());
        // TODO Auto-generated constructor stub

        // Set up listeners
        form.registerForOnDestroy(this);

        mainUIThreadActivity = container.$context();

        // start FunfManger
        Intent i = new Intent(mainUIThreadActivity, FunfManager.class);
        mainUIThreadActivity.startService(i);

        // bind to FunfManger (in case the user wants to set up the
        // schedule)
        doBindService();

        // assign an unique notification id for each probe to use
        PROBE_NOTIFICATION_ID = getNotificationID();

        // get Notification Manager
        String ns = Context.NOTIFICATION_SERVICE;
        mNM = (NotificationManager) mainUIThreadActivity.getSystemService(ns);
        Log.i(TAG, "created notification manager");
        exportPath = new File(Environment.getExternalStorageDirectory(), form.getPackageName()) + File.separator
                + "export";
        exportFormat = NameValueDatabaseService.EXPORT_CSV; // set the exporting format as csv by default

    }

    void doBindService() {
        // Establish a connection with the service. We use an explicit
        // class name because we want a specific service implementation that
        // we know will be running in our own process (and thus won't be
        // supporting component replacement by other applications).
        mainUIThreadActivity.bindService(new Intent(mainUIThreadActivity, FunfManager.class), mConnection,
                Context.BIND_AUTO_CREATE);
        mIsBound = true;
        Log.i(TAG, "FunfManager is bound, and now we could have register dataRequests");

    }

    void doUnbindService() {
        if (mIsBound) {
            // first unrequestData
            unregisterDataRequest();
            // then unregister Pipeline action 
            //      unregisterPipelineActions();
            // Detach our existing connection.
            mainUIThreadActivity.unbindService(mConnection);
            mIsBound = false;
        }
    }

    protected JsonElement getDataRequest(int interval, int duration, String probeName) {
        // This will set the schedule to FunfManger for this probe
        //List<JsonElement> dataRequests = new ArrayList<JsonElement>();
        /*
         * Accepted format for probe configuration {"@type":
         * "edu.mit.media.funf.probe.builtin.BluetoothProbe", "maxScanTime": 40,
         * "@schedule": { "strict": true, "interval": 60, "duration": 30,
         * "opportunistic": true } }
         */

        JsonElement dataRequest = new JsonObject();
        ((JsonObject) dataRequest).addProperty("@type", probeName);
        //((JsonObject) dataRequest).addProperty("maxScanTime", 40);
        JsonElement scheduleObject = new JsonObject();
        ((JsonObject) scheduleObject).addProperty("strict", true);
        ((JsonObject) scheduleObject).addProperty("interval", interval);
        ((JsonObject) scheduleObject).addProperty("duration", duration);
        ((JsonObject) scheduleObject).addProperty("opportunistic", true);

        ((JsonObject) dataRequest).add("@schedule", scheduleObject);

        //dataRequests.add(dataRequest);

        return dataRequest;
    }

    /**
     * Set whether the returned values will be privacy safe (hashed) or not
     */

    @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN, defaultValue = "False")
    @SimpleProperty(description = "If set to True, then sensitive values will be hashed. Note that for some"
            + " application, only hashed values will be good enough. It's not necessary to read clear "
            + "text of user information which can cause some privacy issues.")
    public void HideSensitiveData(boolean hideSensitive) {

        this.privacySafe = hideSensitive;

    }

    /**
     * Indicates whether the returned values will be privacy safe (hashed) or not
     */
    @SimpleProperty(category = PropertyCategory.BEHAVIOR)
    public boolean HideSensitiveData() {
        return privacySafe;
    }

    /**
     * Set the length of the interval for a re-occurring probe activity. 
     * Setting this value triggers the probe using the new schedule 
     * 
     */
    @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_INTEGER, defaultValue = "60")
    @SimpleProperty
    public void SetScheduleInterval(int newInterval) {

        this.interval = newInterval;

    }

    /**
     * Indicates whether the user has specified that the sensor should listen
     * for changes and raise the corresponding events.
     */
    @SimpleProperty(category = PropertyCategory.BEHAVIOR)
    public boolean EnabledSchedule() {
        return enabledSchedule;
    }

    /**
     * Indicates whether the sensor should listen for proximity periodically and
     * raise the corresponding events.
     */

    @SimpleFunction(description = "Enables the sensor periodically and raise the corresponding events")
    public void EnabledSchedule(boolean enabledSchedule) {

        if (this.enabledSchedule != enabledSchedule)
            this.enabledSchedule = enabledSchedule;

        if (enabledSchedule) {

            // register default dataRequest with bound FunfManger
            registerDataRequest(interval, duration); // Everytime we reset the
            // schedule to default
        } else {
            // unregister the dataRequest
            unregisterDataRequest();
        }
    }

    /**
     * Indicates whether the user has specified that the sensor should listen
     * for changes and raise the corresponding events.
     */
    @SimpleProperty(category = PropertyCategory.BEHAVIOR)
    public boolean Enabled() {
        return enabled;
    }

    @Override
    public void onDestroy() {
        // remember to unbind
        doUnbindService();
    }

    // This is used as the database name for all the sensor components that 
    // extends probebase
    public static final String PROBE_BASE_NAME = SensorDbUtil.DB_NAME;

    public static SharedPreferences getSystemPrefs(Context context) {
        // TODO Auto-generated method stub
        return context.getSharedPreferences(ProbeBase.class.getName() + "_system",
                android.content.Context.MODE_PRIVATE);
    }

    protected void saveToDB(IJsonObject completeProbeUri, IJsonObject data) {

        Log.i(TAG, "Writing data: " + completeProbeUri + ": " + data.toString());
        final JsonObject dataObject = data.getAsJsonObject();
        dataObject.add("probe", completeProbeUri.get(RuntimeTypeAdapterFactory.TYPE));
        dataObject.add("timezoneOffset", new JsonPrimitive(localOffsetSeconds)); // nice
        // move
        final long timestamp = data.get(BaseProbeKeys.TIMESTAMP).getAsLong();
        final String probeName = completeProbeUri.get("@type").getAsString();

        Bundle b = new Bundle();
        b.putString(NameValueDatabaseService.DATABASE_NAME_KEY, PROBE_BASE_NAME);
        b.putLong(NameValueDatabaseService.TIMESTAMP_KEY, timestamp);
        b.putString(NameValueDatabaseService.NAME_KEY, probeName);
        b.putString(NameValueDatabaseService.VALUE_KEY, dataObject.toString());
        Intent i = new Intent(mBoundFunfManager, NameValueDatabaseService.class);
        i.setAction(DatabaseService.ACTION_RECORD);
        i.putExtras(b);
        mBoundFunfManager.startService(i);

    }

    /**
     * Export the Sensor Database (SensorData as the name for the sqlite db on Android) as
     * csv file(s) or JSON file(s). Each type of sensor data in the database
     * will be export it as one file.
     * The export path is under SDcard/packageName/export/
     */
    @SimpleFunction(description = "Export all sensor data as CSV files")
    public void ExportSensorDB() {
        Log.i(TAG, "Exporting DB as CSV files");
        Log.i(TAG, "exporting data...at: " + System.currentTimeMillis());

        Bundle b = new Bundle();
        b.putString(NameValueDatabaseService.DATABASE_NAME_KEY, SensorDbUtil.DB_NAME);
        b.putString(NameValueDatabaseService.EXPORT_KEY, this.exportFormat);
        Intent i = new Intent(mBoundFunfManager, NameValueDatabaseService.class);
        i.setAction(DatabaseService.ACTION_EXPORT);
        i.putExtras(b);
        mBoundFunfManager.startService(i);
    }

    @SimpleFunction(description = "Get the path of the foler to which " + "sensor db are exported")
    public String ExportFolderPath() {
        // the real export path is exportPath + "/" + exportformat
        Log.i(TAG, "exportpath:" + this.exportPath + File.separator + this.exportFormat);
        return this.exportPath + File.separator + this.exportFormat;

    }

    /**
     * Return the name of the database in which the sensed data is stored 
     * (actually there's only one db for sensed data (SensorData)
     */
    @SimpleProperty(category = PropertyCategory.BEHAVIOR)
    public String LocalDBName() {
        return ProbeBase.PROBE_BASE_NAME;

    }

    /**
     * Set to indicate whether the returned sensor data should be saved to db automatically
     * 
     * @param enabledSaveToDB
     *            if true, save all data sensed from this sensor to DB automatically
     */

    @SimpleProperty(description = "Save all sensed data from Wifi sensor probe to db automatically")
    public void EnableSaveToDB(boolean enabledSaveToDB) {
        if (this.enabledSaveToDB != enabledSaveToDB)
            this.enabledSaveToDB = enabledSaveToDB;

    }

    /**
     * Indicates whether the sensed data are saved to DB automatically 
     * 
     */

    public boolean EnabledSaveToDB() {
        return enabledSaveToDB;
    }

    /*
     * Add notification with some message and the app (actually it's app.Screen1) it wants to activate
     * @param title 
     * @param text
     * @param enabledSound
     * @param enabledVibrate
     * @param appName
     * @param extraKey 
     * @param extraVal 
     * 
     */

    @SimpleFunction(description = "Create a notication with message to wake up "
            + "another activity when tap on the notification")
    public void CreateNotification(String title, String text, boolean enabledSound, boolean enabledVibrate,
            String packageName, String className, String extraKey, String extraVal) throws ClassNotFoundException {

        Intent activityToLaunch = new Intent(Intent.ACTION_MAIN);

        Log.i(TAG, "packageName: " + packageName);
        Log.i(TAG, "className: " + className);

        // for local AI instance, all classes are under the package
        // "appinventor.ai_test"
        // but for those runs on Google AppSpot(AppEngine), the package name will be
        // "appinventor.ai_GoogleAccountUserName"
        // e.g. pakageName = appinventor.ai_HomerSimpson.HelloPurr
        // && className = appinventor.ai_HomerSimpson.HelloPurr.Screen1

        ComponentName component = new ComponentName(packageName, className);
        activityToLaunch.setComponent(component);
        activityToLaunch.putExtra(extraKey, extraVal);

        Log.i(TAG, "we found the class for intent to send into notificaiton");

        activityToLaunch.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);

        mContentIntent = PendingIntent.getActivity(mainUIThreadActivity, 0, activityToLaunch, 0);

        Long currentTimeMillis = System.currentTimeMillis();
        notification = new Notification(R.drawable.stat_notify_chat, "Activate Notification!", currentTimeMillis);

        Log.i(TAG, "After creating notification");
        notification.contentIntent = mContentIntent;
        notification.flags = Notification.FLAG_AUTO_CANCEL;

        // reset the notification
        notification.defaults = 0;

        if (enabledSound)
            notification.defaults |= Notification.DEFAULT_SOUND;

        if (enabledVibrate)
            notification.defaults |= Notification.DEFAULT_VIBRATE;

        notification.setLatestEventInfo(mainUIThreadActivity, (CharSequence) title, (CharSequence) text,
                mContentIntent);
        Log.i(TAG, "after updated notification contents");
        mNM.notify(PROBE_NOTIFICATION_ID, notification);
        Log.i(TAG, "notified");

    }

    public abstract void Enabled(boolean enabled);

    public abstract void unregisterDataRequest();

    public abstract void registerDataRequest(int interval, int duration);

}