package org.pyneo.tabulae.locus;
import android.Manifest;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.GpsSatellite;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.location.GpsStatus.Listener;
import android.util.Log;
import java.util.ArrayList;
import org.pyneo.tabulae.R;
import org.pyneo.tabulae.Tabulae;
import static org.pyneo.tabulae.locus.Constants.*;
public class LocusService extends Service implements LocationListener, GpsStatus.Listener {
final Messenger mMessenger = new Messenger(new IncomingHandler());
NotificationManager mNotificationManager;
ArrayList<Messenger> mClients = new ArrayList<>();
GpsStatus gpsStatus;
LocationManager locationManager;
boolean myLocationEnabled;
Context context;
Location lastLocation;
float minDistance = 0.0f;
long minTime = 0;
@Override
public void onCreate() {
Log.d(TAG, "LocusService.onCreate");
context = getBaseContext();
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
CharSequence text = getText(R.string.remote_service_started);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, Tabulae.class), 0);
Notification notification = new Notification.Builder(this)
.setSmallIcon(R.drawable.ic_service)
// .setLargeIcon(Icon.createWithResource(this, R.drawable.ic_service))
.setTicker(text)
// .setColor(getResources().getColor(R.color.primary)) // KITKAT
.setWhen(System.currentTimeMillis())
.setContentTitle(getText(R.string.app_label))
.setContentText(text)
.setContentIntent(contentIntent)
.setDeleteIntent(contentIntent)
.setDefaults(0)
.setLights(getResources().getColor(R.color.primary), 600, 400)
.setOngoing(true)
.setAutoCancel(false)
.build();
mNotificationManager.notify(R.id.service_notification_id_location, notification);
myLocationEnabled = enable();
}
@Override
public void onDestroy() {
Log.d(TAG, "LocusService.onDestroy");
mNotificationManager.cancel(R.id.service_notification_id_location);
disable();
}
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
public synchronized void disable() {
if (myLocationEnabled) {
myLocationEnabled = false;
locationManager.removeUpdates(this);
}
}
protected synchronized boolean enable() {
boolean result = false;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M
|| context.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& context.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
disable();
for (String provider : locationManager.getProviders(true)) {
Location location = locationManager.getLastKnownLocation(provider);
lastLocation = betterLocation(lastLocation, location);
}
for (String provider : locationManager.getProviders(true)) {
if (LocationManager.GPS_PROVIDER.equals(provider) || LocationManager.NETWORK_PROVIDER.equals(provider)) {
locationManager.requestLocationUpdates(provider, minTime, minDistance, this);
result = true;
}
}
if (lastLocation != null) {
onLocationChanged(lastLocation);
}
locationManager.addGpsStatusListener(this);
}
return result;
}
// LocationListener:
@Override
public void onLocationChanged(Location location) {
Log.d(TAG, "LocusService.onLocationChanged location=" + location);
if (location != null) {
for (int i = mClients.size() - 1; i >= 0; i--) {
try {
Bundle b = toBundle(location);
if (LocationManager.GPS_PROVIDER.equals(location.getProvider()) && gpsStatus != null) {
toBundle(b, gpsStatus);
}
mClients.get(i).send(Message.obtain(null, R.id.message_locus_set_value, R.id.event_notify_location, 0, b));
}
catch (RemoteException e) {
mClients.remove(i);
}
}
}
}
private Bundle toBundle(Bundle ret, GpsStatus gpsStatus) {
int count_seen = 0;
int count_fix = 0;
for (GpsSatellite s: gpsStatus.getSatellites()) {
count_seen++;
if (s.usedInFix()) {
count_fix++;
}
}
ret.putInt("satellites", count_fix);
ret.putInt("satellites_seen", count_seen);
return ret;
}
// GpsStatus.Listener:
@Override public void onGpsStatusChanged(int event) {
gpsStatus = locationManager.getGpsStatus(gpsStatus);
switch (event) {
case GpsStatus.GPS_EVENT_STARTED:
case GpsStatus.GPS_EVENT_FIRST_FIX:
case GpsStatus.GPS_EVENT_SATELLITE_STATUS:
case GpsStatus.GPS_EVENT_STOPPED:
default:
}
int count_seen = 0;
int count_fix = 0;
for (GpsSatellite s: gpsStatus.getSatellites()) {
count_seen++;
if (s.usedInFix())
count_fix++;
// Log.d(TAG, "getSnr=" + s.getSnr());
}
// Log.d(TAG, "LocusService.onGpsStatusChanged event=" + event
// + ", satellites=" + count_fix + "/" + count_seen + "/" + gpsStatus.getMaxSatellites()
// + ", timeToFirstFix=" + gpsStatus.getTimeToFirstFix()
// );
}
@Override
public void onProviderDisabled(String provider) {
myLocationEnabled = enable();
}
@Override
public void onProviderEnabled(String provider) {
myLocationEnabled = enable();
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
// do nothing
}
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case R.id.message_locus_register_client:
mClients.add(msg.replyTo);
onLocationChanged(lastLocation);
break;
case R.id.message_locus_unregister_client:
mClients.remove(msg.replyTo);
break;
case R.id.message_locus_set_value:
break;
default:
super.handleMessage(msg);
return;
}
if (mClients.size() == 0) {
stopSelf();
}
}
}
// helpers
public static Location betterLocation(Location l1, Location l2) {
if (l1 == null) return l2;
if (l2 == null) return null;
if (l2.isFromMockProvider())
if (l1.isFromMockProvider())
return null;
else
return l1;
if (Math.abs(l1.getElapsedRealtimeNanos() - l2.getElapsedRealtimeNanos()) < 3E9 && l1.hasAccuracy() && l2.hasAccuracy()) {
return l1.getAccuracy() < l2.getAccuracy() ? l1 : l2;
}
if (l1.getElapsedRealtimeNanos() < l2.getElapsedRealtimeNanos()) return l2;
return l1;
}
public static Bundle toBundle(Location location) {
Bundle ret = null;
if (location != null) {
ret = new Bundle(location.getExtras());
ret.putString("provider", location.getProvider());
ret.putLong("elapsed", location.getElapsedRealtimeNanos());
ret.putDouble("latitude", location.getLatitude());
ret.putDouble("longitude", location.getLongitude());
ret.putLong("time", location.getTime());
if (location.hasSpeed()) {
double speed = location.getSpeed() * 3.6;
ret.putDouble("speed", speed);
if (speed != 0) {
ret.putDouble("pace", 60 / speed);
}
else {
ret.putDouble("pace", Double.POSITIVE_INFINITY);
}
}
if (location.hasAccuracy() && location.getAccuracy() != 0) {
ret.putDouble("accuracy", location.getAccuracy());
}
if (location.hasAltitude()) {
ret.putDouble("altitude", location.getAltitude());
}
if (location.hasBearing()) {
ret.putDouble("bearing", location.getBearing());
}
Bundle extras = location.getExtras();
if (extras != null) {
ret.putInt("satellites", extras.getInt("satellites", 0));
}
}
return ret;
}
}