diff --git a/src/com/google/android/location/internal/GoogleLocationManagerService.java b/src/com/google/android/location/internal/GoogleLocationManagerService.java index 1a6603e4..d9279a46 100644 --- a/src/com/google/android/location/internal/GoogleLocationManagerService.java +++ b/src/com/google/android/location/internal/GoogleLocationManagerService.java @@ -30,6 +30,8 @@ import static org.microg.gms.maps.Constants.ACTION_GMS_LOCATION_MANAGER_SERVICE_ public class GoogleLocationManagerService extends Service { private static final String TAG = "GmsLMS"; + + private GoogleLocationManagerServiceImpl impl = new GoogleLocationManagerServiceImpl(this); private AbstractGmsServiceBroker broker = new AbstractGmsServiceBroker() { @Override public void getGoogleLocationManagerService(IGmsCallbacks callback, int versionCode, @@ -38,7 +40,6 @@ public class GoogleLocationManagerService extends Service { callback.onPostInitComplete(0, impl.asBinder(), null); } }; - private GoogleLocationManagerServiceImpl impl = new GoogleLocationManagerServiceImpl(this); @Override public IBinder onBind(Intent intent) { diff --git a/src/org/microg/gms/location/GoogleLocationManager.java b/src/org/microg/gms/location/GoogleLocationManager.java index 330ae811..1766d160 100644 --- a/src/org/microg/gms/location/GoogleLocationManager.java +++ b/src/org/microg/gms/location/GoogleLocationManager.java @@ -6,58 +6,64 @@ import android.content.Context; import android.content.pm.PackageManager; import android.location.Location; import android.location.LocationManager; -import android.os.Bundle; +import com.google.android.gms.location.ILocationListener; import com.google.android.gms.location.LocationRequest; -import com.google.android.gms.location.internal.ILocationListener; -import java.util.HashMap; -import java.util.Map; +import java.util.ArrayList; +import java.util.List; import static android.location.LocationManager.GPS_PROVIDER; import static android.location.LocationManager.NETWORK_PROVIDER; -import static org.microg.gms.maps.Constants.KEY_MOCK_LOCATION; -public class GoogleLocationManager { - private static final String MOCK_PROVIDER = KEY_MOCK_LOCATION; +public class GoogleLocationManager implements LocationChangeListener { + private static final String MOCK_PROVIDER = "mock"; private Context context; private LocationManager locationManager; - private Map lastKnownLocaton = new HashMap<>(); + private RealLocationProvider gpsProvider; + private RealLocationProvider networkProvider; + private MockLocationProvider mockProvider; + private List currentRequests = new ArrayList<>(); public GoogleLocationManager(Context context) { this.context = context; locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); - updateLastKnownLocation(); - } - - private void updateLastKnownLocation() { - lastKnownLocaton.put(GPS_PROVIDER, locationManager.getLastKnownLocation(GPS_PROVIDER)); - lastKnownLocaton.put(NETWORK_PROVIDER, - locationManager.getLastKnownLocation(NETWORK_PROVIDER)); + gpsProvider = new RealLocationProvider(locationManager, GPS_PROVIDER, this); + networkProvider = new RealLocationProvider(locationManager, NETWORK_PROVIDER, this); + mockProvider = new MockLocationProvider(this); } public Location getLastLocation(String packageName) { - if (lastKnownLocaton.get(KEY_MOCK_LOCATION) != null) - return lastKnownLocaton.get(KEY_MOCK_LOCATION); - if (hasFineLocationPermission()) { - Location network = lastKnownLocaton.get(NETWORK_PROVIDER); - Location gps = lastKnownLocaton.get(GPS_PROVIDER); + return getLocation(hasFineLocationPermission(), hasCoarseLocationPermission()); + } + + public Location getLocation(boolean gpsPermission, boolean networkPermission) { + if (mockProvider.getLocation() != null) + return mockProvider.getLocation(); + if (gpsPermission) { + Location network = networkProvider.getLastLocation(); + Location gps = gpsProvider.getLastLocation(); if (network == null) return gps; if (gps == null) return network; - if (gps.getTime() > network.getTime()) + if (gps.getTime() > network.getTime() - 10000) return gps; return network; - } else if (hasCoarseLocationPermission()) { - return lastKnownLocaton.get(NETWORK_PROVIDER); + } else if (networkPermission) { + Location network = networkProvider.getLastLocation(); + if (network.getExtras() != null && + network.getExtras().getParcelable("no_gps_location") instanceof Location) { + network = network.getExtras().getParcelable("no_gps_location"); + } + return network; } return null; } private boolean hasCoarseLocationPermission() { return context.checkCallingPermission(Manifest.permission.ACCESS_COARSE_LOCATION) == - PackageManager.PERMISSION_GRANTED; + PackageManager.PERMISSION_GRANTED || hasFineLocationPermission(); } private boolean hasFineLocationPermission() { @@ -70,37 +76,75 @@ public class GoogleLocationManager { PackageManager.PERMISSION_GRANTED; } + private void requestLocationUpdates(LocationRequestHelper request) { + currentRequests.add(request); + if (request.hasFinePermission && + request.locationRequest.getPriority() == LocationRequest.PRIORITY_HIGH_ACCURACY) + gpsProvider.addRequest(request); + if (request.hasCoarsePermission && + request.locationRequest.getPriority() != LocationRequest.PRIORITY_NO_POWER) + networkProvider.addRequest(request); + } + public void requestLocationUpdates(LocationRequest request, ILocationListener listener, String packageName) { - + requestLocationUpdates( + new LocationRequestHelper(context, request, hasFineLocationPermission(), + hasCoarseLocationPermission(), packageName, listener)); } - public void requestLocationUpdates(LocationRequest request, PendingIntent intent) { - + public void requestLocationUpdates(LocationRequest request, PendingIntent intent, + String packageName) { + requestLocationUpdates( + new LocationRequestHelper(context, request, hasFineLocationPermission(), + hasCoarseLocationPermission(), packageName, intent)); } - public void removeLocationUpdates(ILocationListener listener) { - + private void removeLocationUpdates(LocationRequestHelper request) { + currentRequests.remove(request); + gpsProvider.removeRequest(request); + networkProvider.removeRequest(request); } - public void removeLocationUpdates(PendingIntent intent) { + public void removeLocationUpdates(ILocationListener listener, String packageName) { + for (int i = 0; i < currentRequests.size(); i++) { + if (currentRequests.get(i).respondsTo(listener)) { + removeLocationUpdates(currentRequests.get(i)); + i--; + } + } + } + public void removeLocationUpdates(PendingIntent intent, String packageName) { + for (int i = 0; i < currentRequests.size(); i++) { + if (currentRequests.get(i).respondsTo(intent)) { + removeLocationUpdates(currentRequests.get(i)); + i--; + } + } } public void setMockMode(boolean mockMode) { if (!hasMockLocationPermission()) return; - if (!mockMode) - lastKnownLocaton.put(MOCK_PROVIDER, null); + mockProvider.setMockEnabled(mockMode); } public void setMockLocation(Location mockLocation) { if (!hasMockLocationPermission()) return; - if (mockLocation.getExtras() == null) { - mockLocation.setExtras(new Bundle()); + mockProvider.setLocation(mockLocation); + } + + @Override + public void onLocationChanged() { + for (int i = 0; i < currentRequests.size(); i++) { + LocationRequestHelper request = currentRequests.get(i); + if (!request + .report(getLocation(request.hasFinePermission, request.hasCoarsePermission))) { + removeLocationUpdates(request); + i--; + } } - mockLocation.getExtras().putBoolean(KEY_MOCK_LOCATION, false); - lastKnownLocaton.put(MOCK_PROVIDER, mockLocation); } } diff --git a/src/org/microg/gms/location/GoogleLocationManagerServiceImpl.java b/src/org/microg/gms/location/GoogleLocationManagerServiceImpl.java index 7bdf0621..5c1ceed6 100644 --- a/src/org/microg/gms/location/GoogleLocationManagerServiceImpl.java +++ b/src/org/microg/gms/location/GoogleLocationManagerServiceImpl.java @@ -12,7 +12,7 @@ import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationStatus; import com.google.android.gms.location.internal.IGeofencerCallbacks; import com.google.android.gms.location.internal.IGoogleLocationManagerService; -import com.google.android.gms.location.internal.ILocationListener; +import com.google.android.gms.location.ILocationListener; import com.google.android.gms.location.internal.LocationRequestInternal; import com.google.android.gms.location.places.*; import com.google.android.gms.location.places.internal.IPlacesCallbacks; @@ -89,21 +89,21 @@ public class GoogleLocationManagerServiceImpl extends IGoogleLocationManagerServ public void requestLocationUpdatesWithIntent(LocationRequest request, PendingIntent callbackIntent) throws RemoteException { Log.d(TAG, "requestLocationUpdatesWithIntent: " + request); - getLocationManager().requestLocationUpdates(request, callbackIntent); + getLocationManager().requestLocationUpdates(request, callbackIntent, null); } @Override public void removeLocationUpdatesWithListener(ILocationListener listener) throws RemoteException { Log.d(TAG, "removeLocationUpdatesWithListener: " + listener); - getLocationManager().removeLocationUpdates(listener); + getLocationManager().removeLocationUpdates(listener, null); } @Override public void removeLocationUpdatesWithIntent(PendingIntent callbackIntent) throws RemoteException { Log.d(TAG, "removeLocationUpdatesWithIntent: " + callbackIntent); - getLocationManager().removeLocationUpdates(callbackIntent); + getLocationManager().removeLocationUpdates(callbackIntent, null); } @Override @@ -154,9 +154,9 @@ public class GoogleLocationManagerServiceImpl extends IGoogleLocationManagerServ } @Override - public void requestLocationUpdates(LocationRequest request, ILocationListener listener, + public void requestLocationUpdatesWithPackage(LocationRequest request, ILocationListener listener, String packageName) throws RemoteException { - Log.d(TAG, "requestLocationUpdates: " + request); + Log.d(TAG, "requestLocationUpdatesWithPackage: " + request); getLocationManager().requestLocationUpdates(request, listener, packageName); } diff --git a/src/org/microg/gms/location/LocationChangeListener.java b/src/org/microg/gms/location/LocationChangeListener.java new file mode 100644 index 00000000..c1b5213c --- /dev/null +++ b/src/org/microg/gms/location/LocationChangeListener.java @@ -0,0 +1,5 @@ +package org.microg.gms.location; + +public interface LocationChangeListener { + public void onLocationChanged(); +} diff --git a/src/org/microg/gms/location/LocationRequestHelper.java b/src/org/microg/gms/location/LocationRequestHelper.java new file mode 100644 index 00000000..9023775c --- /dev/null +++ b/src/org/microg/gms/location/LocationRequestHelper.java @@ -0,0 +1,102 @@ +package org.microg.gms.location; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.location.Location; +import android.os.RemoteException; +import android.util.Log; +import com.google.android.gms.location.ILocationListener; +import com.google.android.gms.location.LocationRequest; + +public class LocationRequestHelper { + public static final String TAG = "GmsLocationRequestHelper"; + private final Context context; + public final LocationRequest locationRequest; + public final boolean hasFinePermission; + public final boolean hasCoarsePermission; + public final String packageName; + private ILocationListener listener; + private PendingIntent pendingIntent; + + private Location lastReport; + private int numReports = 0; + + public LocationRequestHelper(Context context, LocationRequest locationRequest, + boolean hasFinePermission, boolean hasCoarsePermission, String packageName, + ILocationListener listener) { + this.context = context; + this.locationRequest = locationRequest; + this.hasFinePermission = hasFinePermission; + this.hasCoarsePermission = hasCoarsePermission; + this.packageName = packageName; + this.listener = listener; + } + + public LocationRequestHelper(Context context, LocationRequest locationRequest, + boolean hasFinePermission, boolean hasCoarsePermission, String packageName, + PendingIntent pendingIntent) { + this.context = context; + this.locationRequest = locationRequest; + this.hasFinePermission = hasFinePermission; + this.hasCoarsePermission = hasCoarsePermission; + this.packageName = packageName; + this.pendingIntent = pendingIntent; + } + + /** + * @return whether to continue sending reports to this {@link LocationRequestHelper} + */ + public boolean report(Location location) { + if (lastReport != null) { + if (location.getTime() - lastReport.getTime() < locationRequest.getFastestInterval()) { + return true; + } + if (location.distanceTo(lastReport) < locationRequest.getSmallestDesplacement()) { + return true; + } + } + Log.d(TAG, "sending Location: " + location); + if (listener != null) { + try { + listener.onLocationChanged(location); + } catch (RemoteException e) { + return false; + } + } else if (pendingIntent != null) { + Intent intent = new Intent(); + intent.putExtra("com.google.android.location.LOCATION", location); + try { + pendingIntent.send(context, 0, intent); + } catch (PendingIntent.CanceledException e) { + return false; + } + } + lastReport = location; + numReports++; + if (numReports >= locationRequest.getNumUpdates()) { + return false; + } + return true; + } + + @Override + public String toString() { + return "LocationRequestHelper{" + + "locationRequest=" + locationRequest + + ", hasFinePermission=" + hasFinePermission + + ", hasCoarsePermission=" + hasCoarsePermission + + ", packageName='" + packageName + '\'' + + ", lastReport=" + lastReport + + '}'; + } + + public boolean respondsTo(ILocationListener listener) { + return this.listener != null && listener != null && + this.listener.asBinder().equals(listener.asBinder()); + } + + public boolean respondsTo(PendingIntent pendingIntent) { + return this.pendingIntent != null && this.pendingIntent.equals(pendingIntent); + } +} diff --git a/src/org/microg/gms/location/MockLocationProvider.java b/src/org/microg/gms/location/MockLocationProvider.java new file mode 100644 index 00000000..080019fc --- /dev/null +++ b/src/org/microg/gms/location/MockLocationProvider.java @@ -0,0 +1,32 @@ +package org.microg.gms.location; + +import android.location.Location; +import android.os.Bundle; + +import static org.microg.gms.maps.Constants.KEY_MOCK_LOCATION; + +public class MockLocationProvider { + private boolean mockEnabled = false; + private Location mockLocation = null; + private final LocationChangeListener changeListener; + + public MockLocationProvider(LocationChangeListener changeListener) { + this.changeListener = changeListener; + } + + public void setMockEnabled(boolean mockEnabled) { + this.mockEnabled = mockEnabled; + } + + public Location getLocation() { + return mockEnabled ? mockLocation : null; + } + + public void setLocation(Location mockLocation) { + if (mockLocation.getExtras() == null) { + mockLocation.setExtras(new Bundle()); + } + mockLocation.getExtras().putBoolean(KEY_MOCK_LOCATION, false); + this.mockLocation = mockLocation; + } +} diff --git a/src/org/microg/gms/location/RealLocationProvider.java b/src/org/microg/gms/location/RealLocationProvider.java new file mode 100644 index 00000000..ab8f411c --- /dev/null +++ b/src/org/microg/gms/location/RealLocationProvider.java @@ -0,0 +1,110 @@ +package org.microg.gms.location; + +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; +import android.os.Looper; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +public class RealLocationProvider { + + public static final String TAG = "GmsRealLocationProvider"; + private Location lastLocation; + private LocationManager locationManager; + private String name; + private final AtomicBoolean connected = new AtomicBoolean(false); + private long connectedMinTime; + private float connectedMinDistance; + private List requests = new ArrayList<>(); + private final LocationChangeListener changeListener; + private LocationListener listener = new LocationListener() { + @Override + public void onLocationChanged(Location location) { + lastLocation = location; + changeListener.onLocationChanged(); + } + + @Override + public void onStatusChanged(String s, int i, Bundle bundle) { + + } + + @Override + public void onProviderEnabled(String s) { + + } + + @Override + public void onProviderDisabled(String s) { + + } + }; + + public RealLocationProvider(LocationManager locationManager, String name, + LocationChangeListener changeListener) { + this.locationManager = locationManager; + this.name = name; + this.changeListener = changeListener; + updateLastLocation(); + } + + private void updateLastLocation() { + lastLocation = locationManager.getLastKnownLocation(name); + } + + public Location getLastLocation() { + if (!connected.get()) { + updateLastLocation(); + } + return lastLocation; + } + + public void addRequest(LocationRequestHelper request) { + Log.d(TAG, name + ": addRequest " + request); + requests.add(request); + updateConnection(); + } + + public void removeRequest(LocationRequestHelper request) { + Log.d(TAG, name + ": removeRequest " + request); + requests.remove(request); + updateConnection(); + } + + private synchronized void updateConnection() { + if (connected.get() && requests.isEmpty()) { + Log.d(TAG, name + ": no longer requesting location update"); + locationManager.removeUpdates(listener); + connected.set(false); + } else if (!requests.isEmpty()) { + long minTime = Long.MAX_VALUE; + float minDistance = Float.MAX_VALUE; + for (LocationRequestHelper request : requests) { + minTime = Math.min(request.locationRequest.getInterval(), minTime); + minDistance = Math + .min(request.locationRequest.getSmallestDesplacement(), minDistance); + } + if (connected.get()) { + if (connectedMinTime != minTime || connectedMinDistance != minDistance) { + locationManager.removeUpdates(listener); + locationManager.requestLocationUpdates(name, minTime, minDistance, listener, + Looper.getMainLooper()); + } + } else { + locationManager.requestLocationUpdates(name, minTime, minDistance, listener, + Looper.getMainLooper()); + } + Log.d(TAG, + name + ": requesting location updates. minTime=" + minTime + " minDistance=" + + minDistance); + connected.set(true); + connectedMinTime = minTime; + connectedMinDistance = minDistance; + } + } +}