Add basic support for location API (request and last location)

This commit is contained in:
mar-v-in 2015-01-11 16:41:57 +01:00
parent c57656d321
commit 3e573917ba
7 changed files with 337 additions and 43 deletions

View File

@ -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) {

View File

@ -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<String, Location> lastKnownLocaton = new HashMap<>();
private RealLocationProvider gpsProvider;
private RealLocationProvider networkProvider;
private MockLocationProvider mockProvider;
private List<LocationRequestHelper> 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);
}
}

View File

@ -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);
}

View File

@ -0,0 +1,5 @@
package org.microg.gms.location;
public interface LocationChangeListener {
public void onLocationChanged();
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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<LocationRequestHelper> 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;
}
}
}