package com.samknows.measurement.TestRunner;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutionException;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.util.Log;
import com.samknows.libcore.SKPorting;
import com.samknows.libcore.SKConstants;
import com.samknows.libcore.SKOperators;
import com.samknows.libcore.SKOperators.ISKQueryCompleted;
import com.samknows.libcore.SKOperators.SKOperators_Return;
import com.samknows.libcore.SKOperators.SKThrottledQueryResult;
import com.samknows.measurement.SKApplication;
import com.samknows.libcore.R;
import com.samknows.measurement.schedule.TestDescription.*;
import com.samknows.measurement.environment.DCSData;
import com.samknows.measurement.environment.NetworkDataCollector;
import com.samknows.measurement.schedule.TestDescription;
import com.samknows.measurement.schedule.TestGroup;
import com.samknows.measurement.schedule.condition.ConditionGroup;
import com.samknows.measurement.schedule.condition.ConditionGroupResult;
import com.samknows.measurement.environment.BaseDataCollector;
import com.samknows.measurement.environment.LocationDataCollector;
import com.samknows.measurement.storage.DBHelper;
import com.samknows.measurement.storage.ResultsContainer;
import com.samknows.measurement.storage.StorageTestResult;
import com.samknows.measurement.storage.TestBatch;
import com.samknows.measurement.storage.TestResultsManager;
import com.samknows.measurement.util.SKDateFormat;
import com.samknows.tests.Param;
import com.samknows.tests.SKAbstractBaseTest;
import com.samknows.tests.TestFactory;
public class TestExecutor {
private static final String JSON_SUBMISSION_TYPE = "submission_type";
private static final String TAG = TestExecutor.class.getName();
private final TestContext tc;
private SKAbstractBaseTest executingTest;
private long lastTestBytes;
private long accumulatedTestBytes;
private Thread startThread = null;
private final ResultsContainer rc;
public SKAbstractBaseTest getExecutingTest() {
return executingTest;
}
public long getAccumulatedTestBytes() {
return accumulatedTestBytes;
}
public ResultsContainer getResultsContainer() {
return rc;
}
public SKThrottledQueryResult mThrottledQueryResult = null;
String mpThrottleResponse = "no throttling";
private JSONArray accumulatedNetworkTypeLocationMetrics = new JSONArray();
public TestContext getTestContext() {
return tc;
}
public void addCustomMetric(JSONObject customMetric) {
SKPorting.sAssert(customMetric.has("type"));
accumulatedNetworkTypeLocationMetrics.put(customMetric);
}
public void addPassiveLocationMetricForTestResult(JSONObject jsonResult) {
boolean forceReportLastKnownAsLocationFalse = false;
List<JSONObject> passiveMetrics = LocationDataCollector.sGetPassiveLocationMetric(forceReportLastKnownAsLocationFalse);
if (passiveMetrics.size() == 0) {
// Nothing to do!
return;
}
int items = passiveMetrics.size();
int i;
for (i = 0; i < items; i++) {
JSONObject item = passiveMetrics.get(i);
try {
// Overwrite the date/time fields...
// ResultsContainer resultsContainer = getResultsContainer();
// JSONObject theTest = resultsContainer.getJSONArrayForTestId(getInternalNameOfExecutingTest());
long timestamp = 0;
try {
timestamp = jsonResult.getLong(DCSData.JSON_TIMESTAMP);
} catch (Exception e) {
int hahah = 1;
}
item.put("type", "location");
item.put(DCSData.JSON_TIMESTAMP, timestamp);
String datetime = jsonResult.getString(DCSData.JSON_DATETIME);
item.put(DCSData.JSON_DATETIME, datetime); // new java.util.Date(timestamp * 1000L).toString());
accumulatedNetworkTypeLocationMetrics.put(item);
} catch (JSONException e) {
SKPorting.sAssert(StorageTestResult.class, false);
}
}
}
public void addPassiveNetworkTypeMetricForTestResult(JSONObject jsonResult) {
// The following should only ever return a List<JSONObject> containing one item!
List<JSONObject> passiveMetrics = NetworkDataCollector.sGetNetworkDataPassiveMetrics();
int items = passiveMetrics.size();
SKPorting.sAssert(StorageTestResult.class, items == 1);
int i;
for (i = 0; i < items; i++) {
JSONObject item = passiveMetrics.get(i);
try {
// Overwrite the date/time fields...
long timestamp = jsonResult.getLong(DCSData.JSON_TIMESTAMP);
item.put(DCSData.JSON_TIMESTAMP, timestamp);
String datetime = jsonResult.getString(DCSData.JSON_DATETIME);
item.put(DCSData.JSON_DATETIME, datetime); // new java.util.Date(timestamp * 1000L).toString());
accumulatedNetworkTypeLocationMetrics.put(item);
} catch (JSONException e) {
SKPorting.sAssert(StorageTestResult.class, false);
}
}
}
public TestExecutor(TestContext tc) {
super();
this.tc = tc;
accumulatedTestBytes = 0L;
rc = new ResultsContainer();
if (SKApplication.getAppInstance().isThrottleQuerySupported() == false) {
// No throttled query!
mThrottledQueryResult = null;
mpThrottleResponse = "no throttling";
} else {
// Fire-off a Throttle test, capture it, and submit when done!
SKOperators operators = SKOperators.getInstance(tc.getContext());
mpThrottleResponse = "timeout";
mThrottledQueryResult = operators.fireThrottledWebServiceQueryWithCallback(new ISKQueryCompleted() {
@Override
public void onQueryCompleted(Exception e, long responseCode,
String responseDataAsString) {
SKPorting.sAssert(getClass(), mThrottledQueryResult.returnCode == SKOperators_Return.SKOperators_Return_FiredThrottleQueryAwaitCallback);
if (e == null) {
Log.d(TestExecutor.class.getName(), "DEBUG - throttle query success, responseCode=(" + responseCode + "), responseDataAsString=(" + responseDataAsString + ")");
if (responseCode == 200) {
switch (responseDataAsString) {
case "YES":
mpThrottleResponse = "throttled";
break;
case "NO":
mpThrottleResponse = "non-throttled";
break;
default:
SKPorting.sAssert(getClass(), false);
mpThrottleResponse = "error";
break;
}
} else {
SKPorting.sAssert(getClass(), false);
// http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
if ((responseCode == 408) // Request Timeout
|| (responseCode == 504) // Gateway Timeout
|| (responseCode == 524) // Timeout
|| (responseCode == 598) // timeout
|| (responseCode == 599) // timeout
) {
mpThrottleResponse = "timeout";
} else {
mpThrottleResponse = "error";
}
}
} else {
Log.d(TestExecutor.class.getName(), "DEBUG - throttle query error, responseCode=(" + responseCode + "), responseDataAsString=(" + responseDataAsString + ")");
//SKLogger.sAssert(getClass(), false);
mpThrottleResponse = "error";
}
}
});
if (mThrottledQueryResult.returnCode == SKOperators_Return.SKOperators_Return_NoThrottleQuery) {
mpThrottleResponse = "no throttling";
}
}
// This is required to be stored in the test Context; otherwise,
// we cannot capture information on failed Conditions!
tc.resultsContainer = rc;
}
public void addRequestedTest(TestDescription td) {
rc.addRequestedTest(td);
}
public ConditionGroupResult execute(ConditionGroup cg) {
ConditionGroupResult ret = new ConditionGroupResult();
if (cg == null) {
return ret;
}
try {
ConditionGroupResult c = (ConditionGroupResult) cg.testBefore(tc).get();
ret.add(c);
//Treat exceptions as failures
} catch (ExecutionException ee) {
SKPorting.sAssertE(this, "Error in running test condition: " + ee.getMessage());
ret.isSuccess = false;
} catch (InterruptedException ie) {
SKPorting.sAssertE(this, "Error in running test condition: " + ie.getMessage());
ret.isSuccess = false;
}
return ret;
}
public ConditionGroupResult execute(ConditionGroup cg, TestDescription td) {
showNotification(tc.getString(R.string.ntf_checking_conditions));
ConditionGroupResult result = execute(cg);
if (result.isSuccess || result.isFailQuiet()) {
executeTest(td, result);
}
SKPorting.sLogD(TAG, "result test: " + (result.isSuccess ? "OK" : "FAIL"));
if (result.isSuccess && cg != null) {
ConditionGroupResult cgr = cg.testAfter(tc);
result.add(cgr);
rc.addCondition(result.getJsonResultArray());
}
if (cg != null) {
cg.release(tc);
}
SKPorting.sLogD(this, "conditionGroup:execute - rc.getJSON()=" + rc.getJSON().toString());
// TestResultsManager.saveResult(tc.getServiceContext(),
// result.results);
cancelNotification();
return result;
}
public SKAbstractBaseTest executeTest(TestDescription td, ConditionGroupResult result) {
try {
List<Param> params = tc.paramsManager.prepareParams(td.params);
executingTest = TestFactory.create(td.type, params);
if (executingTest != null) {
SKPorting.sLogD(TestExecutor.class, "start to execute test: " + td.displayName);
String displayName = td.displayName;
boolean bShowNotification = true;
if (td.type.equalsIgnoreCase(TestFactory.CLOSESTTARGET)) {
if (SKApplication.getAppInstance().getDoesAppDisplayClosestTargetInfo() == false) {
// Do NOT show closest target report!
bShowNotification = false;
}
} else if (td.type.equals(TestFactory.LATENCY)) {
displayName = tc.getString(R.string.latency);
} else if (td.type.equals(TestFactory.DOWNSTREAMTHROUGHPUT)) {
displayName = tc.getString(R.string.download);
} else if (td.type.equals(TestFactory.UPSTREAMTHROUGHPUT)) {
displayName = tc.getString(R.string.upload);
}
if (bShowNotification) {
showNotification(tc.getString(R.string.ntf_running_test) + displayName);
}
//execute the test in a new thread and kill it if it doesn't terminate after
//Constants.WAIT_TEST_BEFORE_ABORT
Thread t = new Thread(new Runnable() {
@Override
public void run() {
executingTest.runBlockingTestToFinishInThisThread();
}
});
t.start();
t.join(SKConstants.WAIT_TEST_BEFORE_ABORT);
if (t.isAlive()) {
SKPorting.sAssertE(this, "Test is still running after " + SKConstants.WAIT_TEST_BEFORE_ABORT / 1000 + " seconds.");
t.interrupt();
t = null;
} else {
lastTestBytes = executingTest.getNetUsage();
accumulatedTestBytes += lastTestBytes;
result.isSuccess = executingTest.isSuccessful();
// TODO MPC - theJsonResult here, can be used to append the Accumulated results!
JSONObject jsonResult = executingTest.getJSONResult();
//SKLogger.d("", jsonResult.toString());//TODO remove in production
addPassiveLocationMetricForTestResult(jsonResult);
addPassiveNetworkTypeMetricForTestResult(jsonResult);
rc.addTestJSONObject(jsonResult);
// // HACK TO INCLUDE THE JUDPJITTER RESULTS
// if (td.type.equalsIgnoreCase("latency")) {
// if (executingTest instanceof LatencyTest) {
// LatencyTest latencyTest = (LatencyTest) executingTest;
// //String[] judp = executingTest.getOutputFields();
// DCSStringBuilder jjitter = new DCSStringBuilder();
// String jitter = "" + latencyTest.getResultJitterMilliseconds();
// String sent = "" + latencyTest.getSentPackets();
// String received = "" + latencyTest.getReceivedPackets();
// jjitter.append("JUDPJITTER");
// jjitter.append("" + SKAbstractBaseTest.sGetUnixTimeStamp());
// jjitter.append(latencyTest.getTestStatus());
// jjitter.append(latencyTest.getTarget());
// jjitter.append(latencyTest.getIpAddress());
// jjitter.append(128); // PACKETSIZE
// jjitter.append(0); // BITRATE
// jjitter.append(0); // DURATION
// jjitter.append(sent); // PACKETS SENT UP
// jjitter.append(sent); // PACKETS SENT DOWN
// jjitter.append(received); // PACKETS RECEIVED UP
// jjitter.append(received); // PACKETS RECEIVED DOWN
// jjitter.append(jitter); // JITTER UP
// jjitter.append(jitter); // JITTER DOWN
// jjitter.append("" + latencyTest.getAverageMicroseconds());
// result.addTestString(jjitter.build());
// } else {
// SKLogger.sAssert(false);
// }
// }
// if (result.isSuccess) {
// tc.paramsManager.processOutParams(out, td.outParamsDescription);
// if (executingTest instanceof LatencyTest) {
// LatencyTest latencyTest = (LatencyTest)executingTest;
// SKAppSettings settings = SK2AppSettings.getInstance();
// settings.saveString("last_latency", "" + latencyTest.getResultLatencyMilliseconds());
// settings.saveString("last_packetloss", "" + latencyTest.getResultLossPercent0To100());
// settings.saveString( "last_jitter", "" + latencyTest.getResultJitterMilliseconds());
// }
// }
SKPorting.sLogD(TAG, "finished execution test: " + td.type);
}
} else {
SKPorting.sAssertE(TAG, "Can't find test for: " + td.type,
new RuntimeException());
SKPorting.sAssert(getClass(), false);
result.isSuccess = false;
}
} catch (Throwable e) {
SKPorting.sAssertE(this, "Error in executing the test. ", e);
SKPorting.sAssert(getClass(), false);
result.isSuccess = false;
} finally {
cancelNotification();
}
return executingTest;
}
public int getProgress0To100() {
if (executingTest != null) {
return executingTest.getProgress0To100();
}
return -1;
}
@SuppressWarnings("deprecation")
public void showNotification(String message) {
String title = SKApplication.getAppInstance().getAppName();
PendingIntent intent = PendingIntent.getService(tc.getContext(),
SKConstants.RC_NOTIFICATION, new Intent(),
PendingIntent.FLAG_UPDATE_CURRENT);
NotificationManager manager = (NotificationManager) tc
.getSystemService(Context.NOTIFICATION_SERVICE);
Notification.Builder builder = new Notification.Builder(tc.getContext());
builder.setAutoCancel(false);
builder.setTicker(title);
//builder.setContentTitle("WhatsApp Notification");
//builder.setContentText("You have a new message");
builder.setSmallIcon(R.drawable.ic_launcher);
builder.setContentIntent(intent);
builder.setOngoing(true);
//builder.setSubText("This is subtext..."); //API level 16
builder.setNumber(100);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
builder.build();
} else {
SKPorting.sAssert(false);
}
Notification myNotification = builder.getNotification();
manager.notify(SKConstants.NOTIFICATION_ID, myNotification);
/*
Notification n = new Notification(R.drawable.icon_notification, message,
System.currentTimeMillis());
n.setLatestEventInfo(tc.getContext(), title, message, intent);
manager.notify(SKConstants.NOTIFICATION_ID, n);
*/
}
public void cancelNotification() {
NotificationManager manager = (NotificationManager) tc
.getSystemService(Context.NOTIFICATION_SERVICE);
manager.cancel(SKConstants.NOTIFICATION_ID);
}
public void startInBackGround() {
startThread = new Thread(new Runnable() {
public void run() {
start();
}
});
startThread.start();
}
public void start() {
for (BaseDataCollector collector : tc.config.dataCollectors) {
if (collector.isEnabled)
collector.start(tc);
}
}
public void stop() {
if (startThread != null) {
for (; ; ) {
try {
if (!startThread.isAlive()) {
break;
}
startThread.join(100);
} catch (InterruptedException ie) {
SKPorting.sAssertE(this, "Ignore InterruptedException while waiting for the start thread to finish");
SKPorting.sAssert(getClass(), false);
}
}
}
for (BaseDataCollector collector : tc.config.dataCollectors) {
if (collector.isEnabled) {
collector.stop(tc);
// TestResultsManager.saveResult(tc.getServiceContext(),
// collector.getOutput());
rc.addMetric(collector.getJSONOutput());
}
}
}
//private long mBatchId = -1;
public ConditionGroupResult executeGroup(TestGroup tg) {
long startTime = System.currentTimeMillis();
List<TestDescription> tds = new ArrayList<>();
for (SCHEDULE_TEST_ID test_id : tg.testIds) {
tds.add(tc.config.findTestById(test_id));
}
ConditionGroup cg = tc.config.getConditionGroup(tg.conditionGroupId);
showNotification(tc.getString(R.string.ntf_checking_conditions));
ConditionGroupResult result = execute(cg);
List<JSONObject> testsResults = new ArrayList<>();
//Run tests only if the conditions are met
if (result.isSuccess) {
for (TestDescription td : tds) {
SKAbstractBaseTest theRunTest = executeTest(td, result);
List<JSONObject> theResult = com.samknows.measurement.storage.StorageTestResult.testOutput(theRunTest, td.type);
if (theResult != null) {
testsResults.addAll(theResult);
}
}
}
if (cg != null) {
ConditionGroupResult cgr = cg.testAfter(tc);
result.add(cgr);
rc.addCondition(result.getJsonResultArray());
cg.release(tc);
}
List<JSONObject> passiveMetrics = new ArrayList<>();
for (BaseDataCollector c : tc.config.dataCollectors) {
if (c.isEnabled) {
for (JSONObject o : c.getPassiveMetric()) {
passiveMetrics.add(o);
}
}
}
JSONObject batch = new JSONObject();
try {
batch.put(TestBatch.JSON_DTIME, startTime);
batch.put(TestBatch.JSON_RUNMANUALLY, "0");
} catch (JSONException je) {
SKPorting.sAssertE(this,
"Error in creating test batch object: " + je.getMessage());
}
DBHelper db = new DBHelper(tc.getContext());
//SKLogger.sAssert(getClass(), mBatchId == -1);
//mBatchId =
db.insertTestBatch(batch, testsResults, passiveMetrics);
cancelNotification();
return result;
}
public ConditionGroupResult executeBackgroundTestGroup(long groupId) {
TestGroup tg = tc.config.findBackgroundTestGroup(groupId);
if (tg == null) {
SKPorting.sAssertE(this, "can not find background test group for id: " + groupId);
} else {
return executeGroup(tg);
}
return new ConditionGroupResult();
}
public ConditionGroupResult execute(long testId) {
TestDescription td = tc.config.findTest(testId);
if (td != null) {
ConditionGroup cg = tc.config
.getConditionGroup(td.conditionGroupId);
return execute(cg, td);
} else {
SKPorting.sAssertE(this, "can not find test for id: " + testId);
}
return new ConditionGroupResult();
}
// public void save(String type, long batchId) {
// SKLogger.sAssert(getClass(), mBatchId == -1);
// mBatchId = batchId;
//
// save(type);
// }
// http://stackoverflow.com/questions/5485759/android-how-to-determine-a-wifi-channel-number-used-by-wifi-ap-network
public static int convertFrequencyToChannel(int freq) {
if (freq >= 2412 && freq <= 2484) {
return (freq - 2412) / 5 + 1;
} else if (freq >= 5170 && freq <= 5825) {
return (freq - 5170) / 5 + 34;
}
return -1;
}
public void save(String type, long batchId) {
//mBatchId = batchId;
//SKLogger.sAssert(getClass(), mBatchId != -1);
rc.addExtra(JSON_SUBMISSION_TYPE, type);
if (batchId != -1) {
rc.addExtra("batch_id", String.valueOf(batchId));
}
try {
int accumulatedNetworkTypeLocationMetricCount = accumulatedNetworkTypeLocationMetrics.length();
int i;
for (i = 0; i < accumulatedNetworkTypeLocationMetricCount; i++) {
JSONObject object = accumulatedNetworkTypeLocationMetrics.getJSONObject(i);
// The following can be useful for debugging!
// object.put("DEBUG_TAG", "DEBUG_TAG");
rc.addMetric(object);
}
// And clear-out any accumulated metrics!
accumulatedNetworkTypeLocationMetrics = new JSONArray();
if ((mThrottledQueryResult != null) &&
(mThrottledQueryResult.returnCode != SKOperators_Return.SKOperators_Return_NoThrottleQuery)
) {
JSONObject throttleResponseMetric = new JSONObject();
throttleResponseMetric.put("type", "carrier_status");
throttleResponseMetric.put("timestamp", mThrottledQueryResult.timestamp);
throttleResponseMetric.put("datetime", mThrottledQueryResult.datetimeUTCSimple);
throttleResponseMetric.put("carrier", mThrottledQueryResult.carrier);
throttleResponseMetric.put("status", mpThrottleResponse);
rc.addMetric(throttleResponseMetric);
}
// Add WIFI metrics!
Context context = SKApplication.getAppInstance();
//TelephonyManager mTelManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
if (connManager != null && wifiManager != null) {
NetworkInfo netInfo = connManager.getActiveNetworkInfo();
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
if (netInfo != null && wifiInfo != null) {
if (netInfo.getType() == ConnectivityManager.TYPE_WIFI) {
JSONObject wifiStateMetric = new JSONObject();
wifiStateMetric.put("type", "wifistate");
Date now = new Date();
wifiStateMetric.put("datetime", SKDateFormat.sGetDateAsIso8601String(now));
wifiStateMetric.put("timestamp", String.valueOf(now.getTime()));
wifiStateMetric.put("rssi", wifiInfo.getRssi());
List<ScanResult> results = null;
try {
results = wifiManager.getScanResults();
} catch (SecurityException e) {
// This has been seen on a very small set of devices...
SKPorting.sAssert(false);
}
if (results != null) {
// The SSID might be returned with "" around it!
String wifiInfoSSID = wifiInfo.getSSID().replace("\"", "");
boolean bFoundWifi = false;
for (ScanResult scanResult : results) {
String scanResultSSID = scanResult.SSID;
if (scanResultSSID.equals(wifiInfoSSID)) {
bFoundWifi = true;
wifiStateMetric.put("frequency", scanResult.frequency);
wifiStateMetric.put("channel", convertFrequencyToChannel(scanResult.frequency));
break;
}
}
SKPorting.sAssert(bFoundWifi);
}
wifiStateMetric.put("linkspeed", wifiInfo.getLinkSpeed());
rc.addMetric(wifiStateMetric);
}
}
}
} catch (JSONException e) {
SKPorting.sAssert(getClass(), false);
}
//SKLogger.sAssert(getClass(), mBatchId != -1);
TestResultsManager.saveResult(tc.getContext(), rc);
}
public long getLastTestByte() {
return lastTestBytes;
}
}