package com.cos598b;
import java.util.List;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
public class MarkovService extends Service {
// value to return if no wifi found
private static final int WIFI_MIN_POWER_LEVEL = -1000;
// alarm codes
private static int WAIT_ALARM_CODE = 101;
private static int SCHEDULED_ALARM_CODE = 102;
/* location model - Markov chain of 10 steps */
private static DataPoint[] loc_steps;
/* location tracking */
private static LocationListener locationListener;
private static Location mLocation = null;
private static Integer mWifiPowerLevel = null;
private static boolean mCollectingData;
private static boolean mServiceRunning;
/* handler for printing to Toast */
Handler toastHandler;
@Override
public int onStartCommand (Intent intent, int flags, int startId) {
if (mServiceRunning == false) {
onStart();
mServiceRunning = true;
}
return super.onStartCommand(intent, flags, startId);
}
/*
* Is the data collection service running
*/
public static boolean isServiceRunning() {
return mServiceRunning;
}
/*
* Stop collecting data
*/
public static void stopService(Context context) {
context.stopService(new Intent(context, MarkovService.class));
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent newintent = new Intent(context, ScheduledAlarmReceiver.class);
PendingIntent operation = PendingIntent.getBroadcast(context, SCHEDULED_ALARM_CODE, newintent, PendingIntent.FLAG_UPDATE_CURRENT);
am.cancel(operation);
}
/*
* Start collecting data
*/
public static void startService(Context context) {
loc_steps = new DataPoint[Consts.NUM_MARKOV_STEPS];
context.startService(new Intent(context, MarkovService.class));
}
/*
* called when the service is started
*/
private void onStart() {
// setup listener for location updates
locationListener = new LocationListener() {
@Override
public void onLocationChanged(Location arg0) {
onLocation(arg0, MarkovService.this);
}
@Override
public void onProviderDisabled(String arg0) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
};
// set up an alarm for every data point
AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent newintent = new Intent(this, ScheduledAlarmReceiver.class);
PendingIntent operation = PendingIntent.getBroadcast(this, SCHEDULED_ALARM_CODE, newintent, PendingIntent.FLAG_UPDATE_CURRENT);
am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), Consts.TIME_GRANULARITY*1000, operation);
}
/*
* called when the alarm for each data point goes off
*/
public synchronized static void onAlarm(Context context) {
mLocation = null;
mWifiPowerLevel = null;
mCollectingData = true;
// start wifi scan
WifiManager wm = (WifiManager) context.getSystemService (Context.WIFI_SERVICE);
if (wm.isWifiEnabled()) {
wm.startScan();
} else {
// uh oh. wifi is probably off
Utils.toast(context, "DroiDTN: Cannot scan for wifi availability. Please check if wifi on.");
mCollectingData = false;
}
// start gps scan
LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE);
criteria.setBearingRequired(true);
criteria.setBearingAccuracy(Criteria.ACCURACY_HIGH);
criteria.setSpeedRequired(true);
criteria.setSpeedAccuracy(Criteria.ACCURACY_HIGH);
try {
lm.requestSingleUpdate(criteria, locationListener, null);
} catch (Exception e) {
// uh oh. GPS is probably off
Utils.toast(context, "DroiDTN: Cannot request location. Please check if the GPS Setting is on.");
mCollectingData = false;
}
// alarm for when we dont get location/scan in time
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent newintent = new Intent(context, WaitAlarmReceiver.class);
PendingIntent operation = PendingIntent.getBroadcast(context, WAIT_ALARM_CODE, newintent, PendingIntent.FLAG_UPDATE_CURRENT);
am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + Consts.MAX_WAIT*1000, operation);
}
/*
* When the timer expires and we still dont have location/scan updates
*/
public synchronized static void onNoResult(Context context) {
if (locationListener != null) {
LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
lm.removeUpdates(locationListener);
}
if (mCollectingData && (mLocation == null || mWifiPowerLevel == null)) {
newPoint(mLocation, mWifiPowerLevel, false, context);
}
mLocation = null;
mWifiPowerLevel = null;
mCollectingData = false;
}
/*
* called when scan results are available
*/
public synchronized static void onScanResults(Context context) {
WifiManager w = (WifiManager) context.getSystemService (Context.WIFI_SERVICE);
mWifiPowerLevel = gotWifi(w.getScanResults(), context);
if (mLocation != null && mCollectingData) {
newPoint(mLocation, mWifiPowerLevel, true, context);
mCollectingData = false;
}
}
/*
* Helper function for determining if wifi is available, and returning max power level of available wifi
*/
private static Integer gotWifi(List<ScanResult> list, Context context) {
Integer wifi_power_level = null;
if (list != null) {
wifi_power_level = WIFI_MIN_POWER_LEVEL;
WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
List<WifiConfiguration> remembered = wm.getConfiguredNetworks();
for (ScanResult result : list) {
for (String ssid : Consts.SSID_WHITELIST) {
if (result.SSID.equals(ssid)) {
if (wifi_power_level == null || wifi_power_level < result.level) {
wifi_power_level = result.level;
}
}
}
for (WifiConfiguration config : remembered) { // check in remembered SSIDs
if (config.SSID.charAt(0) == '\"' && config.SSID.charAt(config.SSID.length()-1) == '\"') { // SSIDs are usually in "", need to strip those out
if (result.SSID.equals(config.SSID.substring(1, config.SSID.length()-1))) {
if (wifi_power_level == null || wifi_power_level < result.level) {
wifi_power_level = result.level;
}
}
}
else if (result.SSID.equals(config.SSID)) {
if (wifi_power_level == null || wifi_power_level < result.level) {
wifi_power_level = result.level;
}
}
}
}
}
return wifi_power_level;
}
/*
* called when gps results are available
*/
private synchronized static void onLocation(Location location, Context context) {
mLocation = location;
if (mWifiPowerLevel != null && mCollectingData) {
newPoint(mLocation, mWifiPowerLevel, true, context);
mCollectingData = false;
}
}
/*
* New data point is available
* Called every 60 seconds
* valid: whether the data point is valid or not (could be invalid if it is missing
* location, scan etc info which could happen if we are inside a building, etc). if
* it is invalid then location and wifiFound are null
* location: location returned by gps location
* wifiFound: whether we had access to wifi at this point (not eventually)
*
*/
private static void newPoint(Location location, Integer wifi_power_level, boolean valid, Context context) {
// if wifi was found
if (wifi_power_level == null) {
wifi_power_level = WIFI_MIN_POWER_LEVEL; // same large negative number
}
// add wifi power level to earlier points
for (int i = 0; i < Consts.NUM_MARKOV_STEPS; i++) {
if (loc_steps[i] != null) {
// mark as having found wifi
loc_steps[i].addWifiPowerLevel(wifi_power_level);
}
}
// store earliest point
DataPoint point_last = loc_steps[Consts.NUM_MARKOV_STEPS-1];
// move stuff up
for (int i = Consts.NUM_MARKOV_STEPS-1; i > 0; i--) {
loc_steps[i] = loc_steps[i-1];
}
loc_steps[0] = null;
// add new point to markov model
DataPoint point_add;
if (valid) {
point_add = new DataPoint(location.getLatitude(), location.getLongitude(), location.getBearing(), System.currentTimeMillis(), location.getSpeed(), location.getAccuracy());
point_add.addWifiPowerLevel(wifi_power_level);
Utils.toast_test(context, "location found. power level is ".concat(Integer.toString(wifi_power_level)));
} else {
point_add = DataPoint.getInvalid();
point_add.addWifiPowerLevel(wifi_power_level);
Utils.toast_test(context, "location not found. power level is ".concat(Integer.toString(wifi_power_level)));
}
loc_steps[0] = point_add;
// add last data point to database
if (point_last != null && point_last.isValid()) {
DatabaseHelper.addPoint(context, point_last);
Utils.toast_test(context, "store point");
}
}
/*
* on creating the service
*/
@Override
public void onCreate()
{
super.onCreate();
// initialize the toast handler
toastHandler = new Handler();
}
/*
* on destroying the service
*/
@Override
public void onDestroy() {
super.onDestroy();
mServiceRunning = false;
}
// helper for showing the toast notification
private void showToast(String string) {
toastHandler.post(new DisplayToast(string));
}
/*
* display a toast from within non-UI thread
*/
private class DisplayToast implements Runnable {
String text;
public DisplayToast(String text){
this.text = text;
}
@Override
public void run(){
Utils.toast_test(getApplicationContext(), text);
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}