Automation/app/src/main/java/com/jens/automation2/location/LocationProvider.java

541 lines
18 KiB
Java

package com.jens.automation2.location;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.telephony.TelephonyManager;
import android.util.Log;
import com.jens.automation2.ActivityMainScreen;
import com.jens.automation2.AutomationService;
import com.jens.automation2.Miscellaneous;
import com.jens.automation2.PointOfInterest;
import com.jens.automation2.R;
import com.jens.automation2.Rule;
import com.jens.automation2.Settings;
import com.jens.automation2.Trigger.Trigger_Enum;
import com.jens.automation2.receivers.ConnectivityReceiver;
import com.jens.automation2.receivers.PhoneStatusListener;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
public class LocationProvider
{
protected static boolean passiveLocationListenerActive = false;
protected static LocationListener passiveLocationListener;
protected static LocationProvider locationProviderInstance = null;
protected AutomationService parentService;
public AutomationService getParentService()
{
return parentService;
}
protected Location currentLocation;
protected static Location currentLocationStaticCopy;
protected static double speed;
protected ArrayList<Location> locationList = new ArrayList<Location>();
protected static Handler speedHandler = null;
protected static boolean speedTimerActive = false;
protected static Calendar etaAtNextPoi = null;
public static Calendar getEtaAtNextPoi()
{
return etaAtNextPoi;
}
public LocationProvider(AutomationService parent)
{
parentService = parent;
locationProviderInstance = this;
startLocationService();
}
public static LocationProvider getInstance()
{
return locationProviderInstance;
}
public Location getCurrentLocation()
{
return currentLocation;
}
public static Location getLastKnownLocation()
{
return currentLocationStaticCopy;
}
public static double getSpeed()
{
return speed;
}
public static void setSpeed(double speed)
{
LocationProvider.speed = speed;
/*
Check if the last location update may be to old.
It could be that for whatever reason we didn't get a recent location, but the current speed
indicates we have moved quite a bit.
*/
Calendar now = Calendar.getInstance();
float distanceToClosestPoi = PointOfInterest.getClosestPOI(getLastKnownLocation()).getLocation().distanceTo(getLastKnownLocation());
long timeInSecondsPassedSinceLastLocationUpdate = (now.getTimeInMillis() - getLastKnownLocation().getTime()) / 1000;
// Could be we were driving towards it instead of away, but we'll ignore that for the moment.
long secondsRequiredForArrival = now.getTimeInMillis()/1000 / (1000 / 60 * 60);
now.add(Calendar.SECOND, (int)secondsRequiredForArrival);
etaAtNextPoi = now;
if(speedTimerActive)
resetSpeedTimer(etaAtNextPoi);
else
startSpeedTimer(etaAtNextPoi);
}
public void setCurrentLocation(Location newLocation, boolean skipVerification)
{
if(newLocation != null)
{
Miscellaneous.logEvent("i", "Location", "Setting location.", 4);
currentLocation = newLocation;
currentLocationStaticCopy = newLocation;
Miscellaneous.logEvent("i", "LocationListener", "Giving update to POI class", 4);
PointOfInterest.positionUpdate(newLocation, parentService, false, skipVerification);
try
{
if (
locationList.size() >= 1
&&
locationList.get(locationList.size() - 1).getTime() == newLocation.getTime()
&&
locationList.get(locationList.size() - 1).getProvider().equals(newLocation.getProvider())
)
{
// This is a duplicate update, do not store it
Miscellaneous.logEvent("i", "LocationListener", "Duplicate location, ignoring.", 4);
}
else
{
Miscellaneous.logEvent("i", "Speed", "Commencing speed calculation.", 4);
// This part keeps the last two location entries to determine the current speed.
locationList.add(newLocation);
if (newLocation.hasSpeed())
{
Miscellaneous.logEvent("i", "Speed", "Location has speed, taking that: " + String.valueOf(newLocation.getSpeed()) + " km/h", 4);
setSpeed(newLocation.getSpeed()); // Take the value that came with the location, that should be more precise
}
else
{
speedCalculation:
if (locationList.size() >= 2)
{
while (locationList.size() > 2)
{
// Remove all entries except for the last 2
Miscellaneous.logEvent("i", "Speed", "About to delete oldest position record until only 2 left. Currently have " + String.valueOf(locationList.size()) + " records.", 4);
locationList.remove(0);
}
/*
The two most recent locations in the list must have a usable accuracy.
*/
for (int i = 0; i < 2; i++)
{
if
(
(locationList.get(i).getProvider().equals(LocationManager.GPS_PROVIDER) && locationList.get(i).getAccuracy() > Settings.satisfactoryAccuracyGps)
||
(locationList.get(i).getProvider().equals(LocationManager.NETWORK_PROVIDER) && locationList.get(i).getAccuracy() > Settings.satisfactoryAccuracyNetwork)
)
{
Miscellaneous.logEvent("i", "Speed", "Not using 2 most recent locations for speed calculation because at least one does not have a satisfactory accuracy: " + locationList.get(i).toString(), 4);
break speedCalculation;
}
}
Miscellaneous.logEvent("i", "Speed", "Trying to calculate speed based on the last locations.", 4);
double currentSpeed;
long timeDifferenceInSeconds = (Math.abs(locationList.get(locationList.size() - 2).getTime() - locationList.get(locationList.size() - 1).getTime())) / 1000; //milliseconds
if (timeDifferenceInSeconds <= Settings.speedMaximumTimeBetweenLocations * 60)
{
double distanceTraveled = locationList.get(locationList.size() - 2).distanceTo(locationList.get(locationList.size() - 1)); //results in meters
if (timeDifferenceInSeconds == 0)
{
Miscellaneous.logEvent("w", "Speed", "No time passed since last position. Can't calculate speed here.", 4);
return;
}
currentSpeed = distanceTraveled / timeDifferenceInSeconds * 3.6; // convert m/s --> km/h
/*
Due to strange factors the time difference might be 0 resulting in mathematical error.
*/
if (Double.isInfinite(currentSpeed) | Double.isNaN(currentSpeed))
Miscellaneous.logEvent("i", "Speed", "Error while calculating speed.", 4);
else
{
Miscellaneous.logEvent("i", "Speed", "Current speed: " + String.valueOf(currentSpeed) + " km/h", 2);
setSpeed(currentSpeed);
// execute matching rules containing speed
ArrayList<Rule> ruleCandidates = Rule.findRuleCandidatesBySpeed();
for (Rule oneRule : ruleCandidates)
{
if(oneRule.getsGreenLight(this.getParentService()))
oneRule.activate(getParentService(), false);
}
}
}
else
Miscellaneous.logEvent("i", "Speed", "Last two locations are too far apart in terms of time. Cannot use them for speed calculation.", 4);
}
else
{
Miscellaneous.logEvent("w", "Speed", "Don't have enough values for speed calculation, yet.", 3);
}
}
}
}
catch (Exception e)
{
Miscellaneous.logEvent("e", "Speed", "Error during speed calculation: " + Log.getStackTraceString(e), 3);
}
AutomationService.updateNotification();
if (AutomationService.isMainActivityRunning(parentService))
ActivityMainScreen.updateMainScreen();
}
else
Miscellaneous.logEvent("w", "Location", "New location given is null. Ignoring.", 5);
}
public void startLocationService()
{
// if(Settings.useAccelerometerForPositioning && !Miscellaneous.isAndroidEmulator())
// {
// accelerometerHandler = new AccelerometerHandler();
// mySensorActivity = new SensorActivity(this);
// }
// startPhoneStateListener
PhoneStatusListener.startPhoneStatusListener(parentService); // also used to mute anouncements during calls
// startConnectivityReceiver
ConnectivityReceiver.startConnectivityReceiver(parentService);
if(Settings.positioningEngine == 0)
{
if(Rule.isAnyRuleUsing(Trigger_Enum.pointOfInterest) | Rule.isAnyRuleUsing(Trigger_Enum.speed))
{
// TelephonyManager telephonyManager = (TelephonyManager) AutomationService.getInstance().getSystemService(Context.TELEPHONY_SERVICE);
// startCellLocationChangedReceiver
if (CellLocationChangedReceiver.isCellLocationChangedReceiverPossible())
{
if (WifiBroadcastReceiver.mayCellLocationReceiverBeActivated())
CellLocationChangedReceiver.startCellLocationChangedReceiver();
}
else
{
/*
Reasons why we may end up here:
- Airplane mode is active
- No phone module present (pure wifi device)
- No SIM card is inserted or it's not unlocked
We'd have to try GPS now to get an initial position.
For permanent use there is no way we could know when it
would make sense to check the position again.
*/
// Trigger a one-time-position-search
Location loc = CellLocationChangedReceiver.getInstance().getLocation("fine");
LocationProvider.getInstance().setCurrentLocation(loc, true);
}
// startPassiveLocationListener
startPassiveLocationListener();
}
}
else
{
// if(Rule.isAnyRuleUsing(Trigger_Enum.pointOfInterest))
// GeofenceIntentService.startService();
}
}
public void stopLocationService()
{
try
{
PhoneStatusListener.stopPhoneStatusListener(parentService);
CellLocationChangedReceiver.stopCellLocationChangedReceiver();
SensorActivity.stopAccelerometerReceiver();
WifiBroadcastReceiver.stopWifiReceiver();
SensorActivity.stopAccelerometerReceiver();
stopPassiveLocationListener();
}
catch(Exception e)
{
Miscellaneous.logEvent("e", "cellReceiver", "Error stopping LocationReceiver: " + Log.getStackTraceString(e), 3);
}
}
public void startPassiveLocationListener()
{
if(!passiveLocationListenerActive)
{
Miscellaneous.logEvent("i", "LocationListener", "Arming passive location listener.", 4);
LocationManager myLocationManager = (LocationManager) parentService.getSystemService(Context.LOCATION_SERVICE);
passiveLocationListener = new MyPassiveLocationListener();
try
{
myLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, Settings.minimumTimeBetweenUpdate, Settings.minimumDistanceChangeForNetworkUpdate, passiveLocationListener);
}
catch(SecurityException e)
{}
passiveLocationListenerActive = true;
}
}
public void stopPassiveLocationListener()
{
if(passiveLocationListenerActive)
{
Miscellaneous.logEvent("i", "LocationListener", "Disarming passive location listener.", 4);
LocationManager myLocationManager = (LocationManager) parentService.getSystemService(Context.LOCATION_SERVICE);
myLocationManager.removeUpdates(passiveLocationListener);
passiveLocationListenerActive = false;
}
}
public class MyPassiveLocationListener implements LocationListener
{
@Override
public void onLocationChanged(Location up2DateLocation)
{
Miscellaneous.logEvent("i", "Location", "Got passive location update, provider: " + up2DateLocation.getProvider(), 3);
setCurrentLocation(up2DateLocation, true);
}
@Override
public void onProviderDisabled(String provider)
{
// TODO Auto-generated method stub
}
@Override
public void onProviderEnabled(String provider)
{
// TODO Auto-generated method stub
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras)
{
// TODO Auto-generated method stub
}
}
public void handleAirplaneMode(boolean state)
{
if(state)
{
Miscellaneous.logEvent("i", "Airplane mode", "CellLocationChangedReceiver will be deactivated due to Airplane mode.", 2);
CellLocationChangedReceiver.stopCellLocationChangedReceiver();
}
else
{
Miscellaneous.logEvent("i", "Airplane mode", "CellLocationChangedReceiver will be activated due to end of Airplane mode.", 2);
CellLocationChangedReceiver.startCellLocationChangedReceiver();
}
}
public void handleRoaming(Boolean roaming)
{
if(roaming)
{
Miscellaneous.logEvent("i", "Roaming", "We're on roaming.", 4);
if(CellLocationChangedReceiver.isCellLocationListenerActive())
{
Miscellaneous.logEvent("i", "Roaming", "Disabling CellLocationChangedReceiver because we're on roaming.", 3);
CellLocationChangedReceiver.stopCellLocationChangedReceiver();
}
}
else
{
Miscellaneous.logEvent("i", "Roaming", "We're not on roaming.", 4);
if(!CellLocationChangedReceiver.isCellLocationListenerActive())
{
Miscellaneous.logEvent("i", "Roaming", "Enabling CellLocationChangedReceiver because we're not on roaming.", 3);
CellLocationChangedReceiver.startCellLocationChangedReceiver();
}
}
}
public void applySettingsAndRules()
{
/*
* This method's purpose is to check settings and rules and determine
* if changes in them require monitors to be started or stopped.
* It takes care only of those which are more expensive.
*/
// TextToSpeech is handled in AutomationService class
Miscellaneous.logEvent("i", "LocationProvider", this.getParentService().getResources().getString(R.string.applyingSettingsAndRules), 3);
// *********** SETTING CHANGES ***********
if(Settings.useWifiForPositioning && !WifiBroadcastReceiver.isWifiListenerActive())
{
Miscellaneous.logEvent("i", "LocationProvider", "Starting WifiReceiver because settings now allow to.", 4);
WifiBroadcastReceiver.startWifiReceiver(this);
}
else if(!Settings.useWifiForPositioning && WifiBroadcastReceiver.isWifiListenerActive())
{
Miscellaneous.logEvent("i", "LocationProvider", "Shutting down WifiReceiver because settings forbid to.", 4);
WifiBroadcastReceiver.stopWifiReceiver();
}
if(Settings.useAccelerometerForPositioning && !SensorActivity.isAccelerometerReceiverActive())
{
Miscellaneous.logEvent("i", "LocationProvider", "Starting accelerometerReceiver because settings now allow to.", 4);
SensorActivity.startAccelerometerReceiver();
}
else if(!Settings.useAccelerometerForPositioning && SensorActivity.isAccelerometerReceiverActive())
{
Miscellaneous.logEvent("i", "LocationProvider", "Shutting down accelerometerReceiver because settings forbid to.", 4);
SensorActivity.stopAccelerometerReceiver();
}
// *********** RULE CHANGES ***********
if(!CellLocationChangedReceiver.isCellLocationListenerActive() && (Rule.isAnyRuleUsing(Trigger_Enum.pointOfInterest) | Rule.isAnyRuleUsing(Trigger_Enum.speed)))
{
Miscellaneous.logEvent("i", "LocationProvider", "Starting NoiseListener CellLocationChangedReceiver because used in a new/changed rule.", 4);
if(CellLocationChangedReceiver.haveAllPermission())
CellLocationChangedReceiver.startCellLocationChangedReceiver();
}
else
{
Miscellaneous.logEvent("i", "LocationProvider", "Shutting down CellLocationChangedReceiver because not used in any rule.", 4);
CellLocationChangedReceiver.stopCellLocationChangedReceiver();
}
AutomationService.updateNotification();
}
public static void startSpeedTimer(Calendar timeOfForcedLocationCheck)
{
if(!speedTimerActive)
{
if(timeOfForcedLocationCheck == null)
{
Miscellaneous.logEvent("i", "SpeedTimer", "Have no value for speed timer. Using 5 minutes in the future.", 4);
timeOfForcedLocationCheck = Calendar.getInstance();
timeOfForcedLocationCheck.add(Calendar.MINUTE, 5);
}
Calendar calendar = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat(Settings.dateFormat);
Miscellaneous.logEvent("i", "SpeedTimer", "Starting SpeedTimer. Next forced location check would be at " + sdf.format(calendar.getTime()), 4);
Message msg = new Message();
msg.what = 1;
if(speedHandler == null)
speedHandler = new SpeedHandler();
speedHandler.sendMessageAtTime(msg, timeOfForcedLocationCheck.getTimeInMillis());
// speedHandler.sendMessageDelayed(msg, delayTime);
speedTimerActive = true;
}
}
public static void stopSpeedTimer()
{
if(speedTimerActive)
{
Miscellaneous.logEvent("i", "SpeedTimer", "Stopping SpeedTimer.", 4);
// Message msg = new Message();
// msg.what = 0;
if(speedHandler == null)
speedHandler = new SpeedHandler();
else
speedHandler.removeMessages(1);
speedTimerActive = false;
}
}
public static void resetSpeedTimer(Calendar timeOfForcedLocationCheck)
{
if(speedTimerActive)
{
if(timeOfForcedLocationCheck == null)
{
Miscellaneous.logEvent("i", "SpeedTimer", "Have no value for speed timer. Using 5 minutes in the future.", 4);
timeOfForcedLocationCheck = Calendar.getInstance();
timeOfForcedLocationCheck.add(Calendar.MINUTE, 5);
}
Calendar calendar = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat(Settings.dateFormat);
Miscellaneous.logEvent("i", "SpeedTimer", "Resetting SpeedTimer. Next forced location check would be at " + sdf.format(calendar.getTime()), 5);
speedHandler.removeMessages(1);
Message msg = new Message();
msg.what = 1;
speedHandler.sendMessageAtTime(msg, timeOfForcedLocationCheck.getTimeInMillis());
// speedHandler.sendMessageDelayed(msg, delayTime);
speedTimerActive = true;
}
else
startSpeedTimer(timeOfForcedLocationCheck);
}
static class SpeedHandler extends Handler
{
@Override
public void handleMessage(Message msg)
{
super.handleMessage(msg);
if(msg.what == 1)
{
// time is up, no cell location updates since x minutes, start accelerometer
String text = "Timer triggered. Based on the last location and speed we may be at a POI. Forcing location update in case CellLocationChangedReceiver didn\'t fire.";
Location currentLocation = CellLocationChangedReceiver.getInstance().getLocation("coarse");
AutomationService.getInstance().getLocationProvider().setCurrentLocation(currentLocation, false);
}
}
}
}