/*
* Copyright 2013 RobustNet Lab, University of Michigan. All Rights Reserved.
*
* 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.mobilyzer.measurements;
import java.io.InvalidClassException;
import java.lang.reflect.Type;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.google.myjson.Gson;
import com.google.myjson.reflect.TypeToken;
import com.mobilyzer.Config;
import com.mobilyzer.MeasurementDesc;
import com.mobilyzer.MeasurementResult;
import com.mobilyzer.MeasurementTask;
import com.mobilyzer.exceptions.MeasurementError;
import com.mobilyzer.measurements.TracerouteTask.TracerouteDesc;
import com.mobilyzer.util.Logger;
import com.mobilyzer.util.MeasurementJsonConvertor;
import com.mobilyzer.util.PhoneUtils;
import android.os.Parcel;
import android.os.Parcelable;
/**
*
* @author Ashkan Nikravesh (ashnik@umich.edu)
*
* Sequential Task is a measurement task that can execute more than one measurement task
* in series.
*/
public class SequentialTask extends MeasurementTask{
private List<MeasurementTask> tasks;
private ExecutorService executor;
// Type name for internal use
public static final String TYPE = "sequential";
// Human readable name for the task
public static final String DESCRIPTOR = "sequential";
private volatile boolean stopFlag;
private long duration;
private volatile MeasurementTask currentTask;
public static class SequentialDesc extends MeasurementDesc {
public ArrayList<MeasurementTask> subTasks;
public SequentialDesc(String key, Date startTime,
Date endTime, double intervalSec, long count, long priority,
int contextIntervalSec, Map<String, String> params)
throws InvalidParameterException {
super(SequentialTask.TYPE, key, startTime, endTime, intervalSec, count,
priority, contextIntervalSec, params);
initializeParams(params);
if (subTasks==null || subTasks.size()==0){
throw new InvalidParameterException("Sequential task must contain at-least one sub task");
}
}
@Override
protected void initializeParams(Map<String, String> params) {
if (params == null) {
return;
}
subTasks=new ArrayList<MeasurementTask>();
String tasksJsonList = null;
if ((tasksJsonList = params.get("tasks")) != null && tasksJsonList.length() > 0){
try {
JSONArray jsonArray = new JSONArray(tasksJsonList);
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObj = jsonArray.optJSONObject(i);
if (jsonObj != null && MeasurementTask.getMeasurementTypes().contains(jsonObj.get("type"))) {
MeasurementTask subTask = MeasurementJsonConvertor.makeMeasurementTaskFromJson(jsonObj);
subTasks.add(subTask);
}
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
@Override
public String getType() {
return SequentialTask.TYPE;
}
@Override
public int describeContents() {
return 0;
}
protected SequentialDesc(Parcel in) {
super(in);
// ClassLoader loader = Thread.currentThread().getContextClassLoader();
subTasks = in.readArrayList(MeasurementTask.class.getClassLoader());
}
public static final Parcelable.Creator<SequentialDesc> CREATOR
= new Parcelable.Creator<SequentialDesc>() {
public SequentialDesc createFromParcel(Parcel in) {
return new SequentialDesc(in);
}
public SequentialDesc[] newArray(int size) {
return new SequentialDesc[size];
}
};
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeList(subTasks);
}
}
@SuppressWarnings("rawtypes")
public static Class getDescClass() throws InvalidClassException {
return SequentialDesc.class;
}
public SequentialTask(MeasurementDesc desc) {
super(new SequentialDesc(desc.key, desc.startTime, desc.endTime,
desc.intervalSec, desc.count, desc.priority, desc.contextIntervalSec,
desc.parameters));
//we are using executor because it has builtin support for execution timeouts.
executor=Executors.newSingleThreadExecutor();
long totalduration=0;
this.tasks=(List<MeasurementTask>)(((SequentialDesc)getDescription()).subTasks).clone();
for(MeasurementTask mt: tasks){
totalduration+=mt.getDuration();
}
this.duration=totalduration;
this.stopFlag=false;
this.currentTask=null;
}
public SequentialTask(MeasurementDesc desc, ArrayList<MeasurementTask> tasks) {
super(new SequentialDesc(desc.key, desc.startTime, desc.endTime,
desc.intervalSec, desc.count, desc.priority, desc.contextIntervalSec,
desc.parameters));
this.tasks=(List<MeasurementTask>) tasks.clone();
//we are using executor because it has builtin support for execution timeouts.
executor=Executors.newSingleThreadExecutor();
long totalduration=0;
for(MeasurementTask mt: tasks){
totalduration+=mt.getDuration();
}
this.duration=totalduration;
this.stopFlag=false;
this.currentTask=null;
}
protected SequentialTask(Parcel in) {
super(in);
// ClassLoader loader = Thread.currentThread().getContextClassLoader();
// we cannot directly cast Parcelable[] to MeasurementTask[]. Cast them one-by-one
Parcelable[] tempTasks = in.readParcelableArray(MeasurementTask.class.getClassLoader());
executor=Executors.newSingleThreadExecutor();
tasks = new ArrayList<MeasurementTask>();
long totalduration=0;
for ( Parcelable pTask : tempTasks ) {
MeasurementTask mt = (MeasurementTask) pTask;
tasks.add(mt);
totalduration+=mt.getDuration();
}
this.duration = totalduration;
this.stopFlag=false;
this.currentTask=null;
}
public static final Parcelable.Creator<SequentialTask> CREATOR
= new Parcelable.Creator<SequentialTask>() {
public SequentialTask createFromParcel(Parcel in) {
return new SequentialTask(in);
}
public SequentialTask[] newArray(int size) {
return new SequentialTask[size];
}
};
@Override
public int describeContents() {
return super.describeContents();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeParcelableArray(tasks.toArray(new MeasurementTask[tasks.size()]), flags);
}
@Override
public String getDescriptor() {
return DESCRIPTOR;
}
@Override
public MeasurementResult[] call() throws MeasurementError {
ArrayList<MeasurementResult> allResults=new ArrayList<MeasurementResult>();
try {
// futures=executor.invokeAll(this.tasks,timeout,TimeUnit.MILLISECONDS);
for(MeasurementTask mt: tasks){
if(stopFlag){
throw new MeasurementError("Cancelled");
}
/*
if(allResults.size()>0 && mt.getType().equals(TracerouteTask.TYPE) && ((TracerouteDesc)(mt.getDescription())).preCondition!=null){
String precond=((TracerouteDesc)(mt.getDescription())).preCondition;
Logger.d(">>>>>>>>>>>>>>> "+precond);
PhoneUtils phoneUtils = PhoneUtils.getPhoneUtils();
String network_type=phoneUtils.getNetwork().replace(" ", "").toLowerCase();
float rttThreshold=-1;
Type listType = new TypeToken<ArrayList<ArrayList<String>>>() {}.getType();
List<ArrayList<String>> precondList = new Gson().fromJson(precond, listType);
for(ArrayList<String> condition: precondList){
if(condition.size()==3){//WiFi precondition
String cond_nettype=condition.get(0);
String cond_ssid=condition.get(1);
float cond_rtt=Float.parseFloat(condition.get(2));
if(cond_nettype.equals(network_type) && cond_ssid.equals(phoneUtils.getWifiSSID())
&& cond_rtt>0){
Logger.i("Precond matched: "+cond_nettype+" "+cond_ssid);
rttThreshold=cond_rtt;
break;
}
}else if(condition.size()==2){//cellular precondition
String cond_nettype=condition.get(0);
float cond_rtt=Float.parseFloat(condition.get(1));
if(cond_nettype.equals(network_type) && cond_rtt>0){
Logger.i("Precond matched: "+cond_nettype);
rttThreshold=cond_rtt;
break;
}
}
}
float pingRtt=-1;
if(allResults.get(allResults.size()-1).getValues().get("min_rtt_ms")!=null){
pingRtt=Float.parseFloat(allResults.get(allResults.size()-1).getValues().get("min_rtt_ms"));
}
if(rttThreshold==-1 || pingRtt==-1 || rttThreshold>pingRtt){
Logger.i("Traceroute diag task is not going to run because: "+rttThreshold+" "+pingRtt);
break;
}else if(rttThreshold!=-1 && pingRtt!=-1){
allResults.get(allResults.size()-1).getValues().put("rtt_thresh", rttThreshold+"");
}
}
*/
Logger.i("Sub task "+mt.getType()+" is going to run");
Future<MeasurementResult[]> f=executor.submit(mt);
currentTask=mt;
MeasurementResult[] results;
//specifying timeout for each task based on its duration
try {
results = f.get( mt.getDuration()==0 ?
Config.DEFAULT_TASK_DURATION_TIMEOUT * 2 : mt.getDuration() * 2,
TimeUnit.MILLISECONDS);
for(int i=0;i<results.length;i++){
allResults.add(results[i]);
}
} catch (TimeoutException e) {
if(mt.stop()){
f.cancel(true);
}
}
}
} catch (InterruptedException e) {
Logger.e("Sequential task " + this.getTaskId() + " got interrupted!");
}catch (ExecutionException e) {
throw new MeasurementError("Execution error: " + e.getCause());
}
finally{
executor.shutdown();
}
return (MeasurementResult[])allResults.toArray(
new MeasurementResult[allResults.size()]);
}
@Override
public String getType() {
return SequentialTask.TYPE;
}
@Override
public MeasurementTask clone() {
MeasurementDesc desc = this.measurementDesc;
SequentialDesc newDesc = new SequentialDesc(desc.key, desc.startTime, desc.endTime,
desc.intervalSec, desc.count, desc.priority, desc.contextIntervalSec, desc.parameters);
ArrayList<MeasurementTask> newTaskList=new ArrayList<MeasurementTask>();
for(MeasurementTask mt: tasks){
newTaskList.add(mt.clone());
}
return new SequentialTask(newDesc,newTaskList);
}
@Override
public boolean stop() {
if(currentTask.stop()){
stopFlag=true;
executor.shutdown();
return true;
}else{
return false;
}
}
@Override
public long getDuration() {
return duration;
}
@Override
public void setDuration(long newDuration) {
if(newDuration<0){
this.duration=0;
}else{
this.duration=newDuration;
}
}
public MeasurementTask[] getTasks() {
return tasks.toArray(new MeasurementTask[tasks.size()]);
}
//TODO
@Override
public long getDataConsumed() {
return 0;
}
}