/**
* 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.probe;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import edu.mit.media.funf.Utils;
import edu.mit.media.funf.probe.Probe.Parameter;
import edu.mit.media.funf.probe.Probe.Parameter.Builtin;
/**
* Schedules probes based on PERIOD, DURATION, START, END,and PASSIVE parameters.
* This class is naive to all other parameters. Classes requiring other parameters,
* should extend this class to ensure those parameters are merged correctly.
*
*/
public class DefaultProbeScheduler implements ProbeScheduler {
/* (non-Javadoc)
* @see edu.mit.media.funf.probe.ProbeScheduler#shouldBeEnabled(edu.mit.media.funf.probe.Probe, java.util.Collection)
*/
@Override
public boolean shouldBeEnabled(Probe probe, Collection<Intent> requests) {
return requests != null && !requests.isEmpty(); // Has a requester
}
/* (non-Javadoc)
* @see edu.mit.media.funf.probe.ProbeScheduler#startRunningNow(edu.mit.media.funf.probe.Probe, android.content.Intent[])
*/
@Override
public Bundle startRunningNow(Probe probe, Collection<Intent> requests) {
// Merge parameters
Bundle params = mergeParameters(probe, requests);
if (params == null) {
return null;
}
// Check if should run now
long period = Utils.secondsToMillis(Utils.getLong(params, Parameter.Builtin.PERIOD.name, 0L));
long startTime = Utils.secondsToMillis(Utils.getLong(params, Parameter.Builtin.START.name, 0L));
long endTime = Utils.secondsToMillis(Utils.getLong(params, Parameter.Builtin.END.name, 0L));
long mostRecentTimeRun = Utils.secondsToMillis(probe.getPreviousRunTime());
long currentTime = System.currentTimeMillis();
boolean shouldRun = (startTime == 0 || startTime <= currentTime) // After start time (if exists)
&& (endTime == 0 || currentTime <= endTime) // Before end time (if exists)
&& (period == 0 || (mostRecentTimeRun + period) <= currentTime);
if (shouldRun) {
// Register stop alarm
// 0L duration if duration param is not supported for probe. Stop queued up immediately.
long duration = Utils.secondsToMillis(params.getLong(Parameter.Builtin.DURATION.name, 0L));
if (duration > 0L) {
scheduleAlarm(probe, Probe.ACTION_STOP, currentTime + duration);
}
return params;
} else {
return null;
}
}
/* (non-Javadoc)
* @see edu.mit.media.funf.probe.ProbeScheduler#scheduleNextRun(edu.mit.media.funf.probe.Probe, android.content.Intent[])
*/
@Override
public Long scheduleNextRun(Probe probe, Collection<Intent> requests) {
Parameter periodParam = getAvailableParameter(probe, Parameter.Builtin.PERIOD);
if (requests == null || periodParam == null) {
return null;
}
Bundle params = mergeParameters(probe, requests);
if (params == null) {
return null;
}
long period = Utils.secondsToMillis(Utils.getLong(params, Parameter.Builtin.PERIOD.name, 0L));
long mostRecentTimeRun = Utils.secondsToMillis(probe.getPreviousRunTime());
if (probe.isAvailableOnDevice() && period != 0L) {
ArrayList<Bundle> dataRequests = new ArrayList<Bundle>();
for (Intent request : requests) {
ArrayList<Bundle> b = Utils.getArrayList(request.getExtras(), Probe.REQUESTS_KEY);
dataRequests.addAll(b);
}
//Log.i(TAG, "Requests: " + requests);
//Log.i(TAG, "Data requests: " + dataRequests.toString());
//Log.i(TAG, "Final params: " + params.toString());
//Log.i(TAG, "Running from DefaultProbeScheduler, mostRecentRunTime: " + mostRecentTimeRun + " + period: " + period);
scheduleAlarm(probe, Probe.ACTION_RUN, mostRecentTimeRun + period);
}
return Utils.millisToSeconds(mostRecentTimeRun + period);
}
/**
* Sets the alarm for this probe.
* @param probe
* @param action
* @param time
*/
protected void scheduleAlarm(Probe probe, String action, long time) {
Intent intent = new Intent(action, null, probe, probe.getClass());
PendingIntent pi = PendingIntent.getService(probe, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager manager = (AlarmManager)probe.getSystemService(Context.ALARM_SERVICE);
manager.set(AlarmManager.RTC_WAKEUP, time, pi);
}
/**
* Uses all of the outstanding data requests to determine the parameters the probe will run with.
* @param probe
* @param requests
* @return
*/
protected Bundle mergeParameters(Probe probe, Collection<Intent> requests) {
boolean hasRequests = false;
for (Intent request : requests) {
ArrayList<Bundle> dataRequests = Utils.getArrayList(request.getExtras(), Probe.REQUESTS_KEY);
if (dataRequests != null && !dataRequests.isEmpty()) {
hasRequests = true;
break;
}
}
if (!hasRequests) {
return null;
}
Bundle params = new Bundle();
putMergedParam(params, getAvailableParameter(probe, Parameter.Builtin.PERIOD), requests, false);
putMergedParam(params, getAvailableParameter(probe, Parameter.Builtin.DURATION), requests, true);
putMergedParam(params, getAvailableParameter(probe, Parameter.Builtin.START), requests, false);
putMergedParam(params, getAvailableParameter(probe, Parameter.Builtin.END), requests, true);
// Other parameters, just pick max
// TODO: allow user to specify comparator
List<String> knownParameters = Arrays.asList(Parameter.Builtin.PERIOD.name, Parameter.Builtin.DURATION.name, Parameter.Builtin.START.name, Parameter.Builtin.END.name);
Parameter[] availableParameters = getParametersNotNull(probe.getAvailableParameters());
for (Parameter parameter : availableParameters) {
if (!knownParameters.contains(parameter.getName())) {
putUnknownMergedParam(params, parameter, requests, true);
}
}
return params;
}
public static void putUnknownMergedParam(Bundle params, Probe.Parameter parameter, Collection<Intent> requests, boolean returnLargest) {
if (parameter == null || requests == null || requests.isEmpty()) {
return;
}
Comparable defaultValue = (Comparable)parameter.getValue();
String paramName = parameter.getName();
Comparable mergedValue = null;
for(Intent request : requests) {
ArrayList<Bundle> dataRequests = Utils.getArrayList(request.getExtras(), Probe.REQUESTS_KEY);
for (Bundle dataRequest : dataRequests) {
Comparable value = (Comparable)dataRequest.get(paramName);
if (value == null) {
continue;
}
if (mergedValue == null) {
mergedValue = value;
continue;
}
int comparison = value.compareTo(mergedValue);
if (!returnLargest) {
comparison *= -1;
}
if (comparison > 0) {
value = mergedValue;
}
}
}
if (mergedValue != null) {
Utils.putInBundle(params, paramName, mergedValue);
}
}
public static void putMergedParam(Bundle params, Probe.Parameter parameter, Collection<Intent> requests, boolean returnLargest) {
if (parameter == null || requests == null || requests.isEmpty()) {
return;
}
long defaultValue = (Long)parameter.getValue();
String paramName = parameter.getName();
long mergedValue = returnLargest ? Long.MIN_VALUE : Long.MAX_VALUE;
for(Intent request : requests) {
ArrayList<Bundle> dataRequests = Utils.getArrayList(request.getExtras(), Probe.REQUESTS_KEY);
for (Bundle dataRequest : dataRequests) {
long value = Utils.getLong(dataRequest, paramName, defaultValue);
if ((returnLargest && value > mergedValue) || (!returnLargest && value < mergedValue)) {
mergedValue = value;
}
}
}
params.putLong(paramName, mergedValue);
}
public static Parameter getAvailableParameter(Probe probe, Builtin systemParam) {
return Parameter.getAvailableParameter(DefaultProbeScheduler.getParametersNotNull(probe.getAvailableParameters()), systemParam);
}
public static Parameter[] getParametersNotNull(Parameter[] availableParameters) {
return (availableParameters == null) ? new Parameter[] {} : availableParameters;
}
public static Bundle getDefaultParameters(Probe probe) {
Bundle params = new Bundle();
for(Parameter param : getParametersNotNull(probe.getAvailableParameters())) {
Utils.putInBundle(params, param.getName(), param.getValue());
}
return params;
}
public static Bundle getCompleteParams(Probe probe, Bundle params) {
if (params == null) {
return null;
}
Bundle completeParams = getDefaultParameters(probe);
// TODO: only use parameters that are specified
// for (Parameter param : probe.getAvailableParameters()) {
// Utils.putInBundle(params, param.getName(), param.getValue());
//}
completeParams.putAll(params);
return completeParams;
}
}