diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7c0e18bf..b0fcdc8e 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2,5 +2,8 @@
-
+
+
+
+
diff --git a/src/org/microg/gms/common/GmsClient.java b/src/org/microg/gms/common/GmsClient.java
index c9b726f7..e8cd12ae 100644
--- a/src/org/microg/gms/common/GmsClient.java
+++ b/src/org/microg/gms/common/GmsClient.java
@@ -10,7 +10,6 @@ import android.os.RemoteException;
import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
-import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.internal.IGmsCallbacks;
import com.google.android.gms.common.internal.IGmsServiceBroker;
@@ -21,9 +20,9 @@ public abstract class GmsClient implements ApiConnection {
private static final String TAG = "GmsClient";
private final Context context;
- private final GoogleApiClient.ConnectionCallbacks callbacks;
- private final GoogleApiClient.OnConnectionFailedListener connectionFailedListener;
- private ConnectionState state = ConnectionState.NOT_CONNECTED;
+ protected final GoogleApiClient.ConnectionCallbacks callbacks;
+ protected final GoogleApiClient.OnConnectionFailedListener connectionFailedListener;
+ protected ConnectionState state = ConnectionState.NOT_CONNECTED;
private ServiceConnection serviceConnection;
private I serviceInterface;
@@ -46,18 +45,21 @@ public abstract class GmsClient implements ApiConnection {
Log.d(TAG, "connect()");
if (state == ConnectionState.CONNECTED || state == ConnectionState.CONNECTING) return;
state = ConnectionState.CONNECTING;
- if (GooglePlayServicesUtil.isGooglePlayServicesAvailable(context) !=
- ConnectionResult.SUCCESS) {
- state = ConnectionState.NOT_CONNECTED;
- } else {
- if (serviceConnection != null) {
- MultiConnectionKeeper.getInstance(context)
- .unbind(getActionString(), serviceConnection);
- }
- serviceConnection = new GmsServiceConnection();
- MultiConnectionKeeper.getInstance(context).bind(getActionString(),
- serviceConnection);
+ if (serviceConnection != null) {
+ MultiConnectionKeeper.getInstance(context)
+ .unbind(getActionString(), serviceConnection);
}
+ serviceConnection = new GmsServiceConnection();
+ if (!MultiConnectionKeeper.getInstance(context).bind(getActionString(),
+ serviceConnection)) {
+ state = ConnectionState.ERROR;
+ handleConnectionFailed();
+ }
+ }
+
+ public void handleConnectionFailed() {
+ connectionFailedListener.onConnectionFailed(new ConnectionResult(ConnectionResult
+ .API_UNAVAILABLE, null));
}
@Override
@@ -78,7 +80,7 @@ public abstract class GmsClient implements ApiConnection {
@Override
public boolean isConnected() {
- return state == ConnectionState.CONNECTED;
+ return state == ConnectionState.CONNECTED || state == ConnectionState.PSEUDO_CONNECTED;
}
@Override
@@ -86,6 +88,10 @@ public abstract class GmsClient implements ApiConnection {
return state == ConnectionState.CONNECTING;
}
+ public boolean hasError() {
+ return state == ConnectionState.ERROR;
+ }
+
public Context getContext() {
return context;
}
@@ -94,8 +100,8 @@ public abstract class GmsClient implements ApiConnection {
return serviceInterface;
}
- private enum ConnectionState {
- NOT_CONNECTED, CONNECTING, CONNECTED, DISCONNECTING, ERROR
+ protected enum ConnectionState {
+ NOT_CONNECTED, CONNECTING, CONNECTED, DISCONNECTING, ERROR, PSEUDO_CONNECTED
}
private class GmsServiceConnection implements ServiceConnection {
diff --git a/src/org/microg/gms/location/LocationClientImpl.java b/src/org/microg/gms/location/LocationClientImpl.java
index 4ce3a12a..57c9c60b 100644
--- a/src/org/microg/gms/location/LocationClientImpl.java
+++ b/src/org/microg/gms/location/LocationClientImpl.java
@@ -3,6 +3,7 @@ package org.microg.gms.location;
import android.app.PendingIntent;
import android.content.Context;
import android.location.Location;
+import android.os.Bundle;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
@@ -20,6 +21,9 @@ import java.util.Map;
public class LocationClientImpl extends GoogleLocationManagerClient {
private static final String TAG = "GmsLocationClientImpl";
+ private NativeLocationClientImpl nativeLocation = null;
+ private Map listenerMap = new HashMap<>();
+
public LocationClientImpl(Context context, GoogleApiClient.ConnectionCallbacks callbacks,
GoogleApiClient.OnConnectionFailedListener connectionFailedListener) {
@@ -35,50 +39,89 @@ public class LocationClientImpl extends GoogleLocationManagerClient {
return null;
}
- private Map listenerMap = new HashMap<>();
-
public Location getLastLocation() throws RemoteException {
Log.d(TAG, "getLastLocation()");
- return getServiceInterface().getLastLocation();
+ if (nativeLocation != null) {
+ return nativeLocation.getLastLocation();
+ } else {
+ return getServiceInterface().getLastLocation();
+ }
}
public void requestLocationUpdates(LocationRequest request, final LocationListener listener)
throws RemoteException {
- ILocationListener iLocationListener = new ILocationListener.Stub() {
- @Override
- public void onLocationChanged(Location location) throws RemoteException {
- listener.onLocationChanged(location);
+ if (nativeLocation != null) {
+ nativeLocation.requestLocationUpdates(request, listener);
+ } else {
+ if (!listenerMap.containsKey(listener)) {
+ listenerMap.put(listener, new ILocationListener.Stub() {
+ @Override
+ public void onLocationChanged(Location location) throws RemoteException {
+ listener.onLocationChanged(location);
+ }
+ });
}
- };
- listenerMap.put(listener, iLocationListener);
- getServiceInterface().requestLocationUpdatesWithPackage(request,
- iLocationListener, getContext().getPackageName());
+ getServiceInterface().requestLocationUpdatesWithPackage(request,
+ listenerMap.get(listener), getContext().getPackageName());
+ }
}
public void requestLocationUpdates(LocationRequest request, PendingIntent pendingIntent)
throws RemoteException {
- getServiceInterface().requestLocationUpdatesWithIntent(request, pendingIntent);
+ if (nativeLocation != null) {
+ nativeLocation.requestLocationUpdates(request, pendingIntent);
+ } else {
+ getServiceInterface().requestLocationUpdatesWithIntent(request, pendingIntent);
+ }
}
public void requestLocationUpdates(LocationRequest request, LocationListener listener,
Looper looper) throws RemoteException {
+ if (nativeLocation != null) {
+ nativeLocation.requestLocationUpdates(request, listener, looper);
+ }
requestLocationUpdates(request, listener); // TODO
}
public void removeLocationUpdates(LocationListener listener) throws RemoteException {
- getServiceInterface().removeLocationUpdatesWithListener(listenerMap.get(listener));
- listenerMap.remove(listener);
+ if (nativeLocation != null) {
+ nativeLocation.removeLocationUpdates(listener);
+ } else {
+ getServiceInterface().removeLocationUpdatesWithListener(listenerMap.get(listener));
+ }
}
public void removeLocationUpdates(PendingIntent pendingIntent) throws RemoteException {
- getServiceInterface().removeLocationUpdatesWithIntent(pendingIntent);
+ if (nativeLocation != null) {
+ nativeLocation.removeLocationUpdates(pendingIntent);
+ } else {
+ getServiceInterface().removeLocationUpdatesWithIntent(pendingIntent);
+ }
}
public void setMockMode(boolean isMockMode) throws RemoteException {
- getServiceInterface().setMockMode(isMockMode);
+ if (nativeLocation != null) {
+ nativeLocation.setMockMode(isMockMode);
+ } else {
+ getServiceInterface().setMockMode(isMockMode);
+ }
}
public void setMockLocation(Location mockLocation) throws RemoteException {
- getServiceInterface().setMockLocation(mockLocation);
+ if (nativeLocation != null) {
+ nativeLocation.setMockLocation(mockLocation);
+ } else {
+ getServiceInterface().setMockLocation(mockLocation);
+ }
+ }
+
+ @Override
+ public void handleConnectionFailed() {
+ // DO NOT call super here, because fails are not really problems :)
+ nativeLocation = new NativeLocationClientImpl(this);
+ state = ConnectionState.PSEUDO_CONNECTED;
+ Bundle bundle = new Bundle();
+ bundle.putBoolean("fallback_to_native_active", true);
+ callbacks.onConnected(bundle);
}
}
diff --git a/src/org/microg/gms/location/NativeLocationClientImpl.java b/src/org/microg/gms/location/NativeLocationClientImpl.java
new file mode 100644
index 00000000..1937c6c1
--- /dev/null
+++ b/src/org/microg/gms/location/NativeLocationClientImpl.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2014-2015 µg Project Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.microg.gms.location;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.location.Criteria;
+import android.location.Location;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.os.Looper;
+import android.util.Log;
+
+import com.google.android.gms.location.LocationListener;
+import com.google.android.gms.location.LocationRequest;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class NativeLocationClientImpl {
+ private final static String TAG = "GmsToNativeLocationClient";
+ private final static Criteria DEFAULT_CRITERIA = new Criteria();
+ private final static Map pendingCount = new HashMap<>();
+ private final static Map nativePendingMap = new HashMap<>();
+ private static final String EXTRA_PENDING_INTENT = "pending_intent";
+
+ private final Context context;
+ private final LocationManager locationManager;
+ private final Map nativeListenerMap = new HashMap<>();
+
+ public NativeLocationClientImpl(LocationClientImpl client) {
+ context = client.getContext();
+ locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
+ }
+
+ private static Criteria makeNativeCriteria(LocationRequest request) {
+ Criteria criteria = new Criteria();
+ switch (request.getPriority()) {
+ case LocationRequest.PRIORITY_HIGH_ACCURACY:
+ criteria.setAccuracy(Criteria.ACCURACY_FINE);
+ criteria.setPowerRequirement(Criteria.POWER_HIGH);
+ break;
+ case LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY:
+ default:
+ criteria.setAccuracy(Criteria.ACCURACY_COARSE);
+ criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
+ break;
+ case LocationRequest.PRIORITY_NO_POWER:
+ case LocationRequest.PRIORITY_LOW_POWER:
+ criteria.setAccuracy(Criteria.ACCURACY_COARSE);
+ criteria.setPowerRequirement(Criteria.POWER_LOW);
+ }
+ return criteria;
+ }
+
+ public Location getLastLocation() {
+ Log.d(TAG,"getLastLocation()");
+ return locationManager.getLastKnownLocation(
+ locationManager.getBestProvider(DEFAULT_CRITERIA, true));
+ }
+
+ public void requestLocationUpdates(LocationRequest request, LocationListener listener) {
+ requestLocationUpdates(request, listener, Looper.getMainLooper());
+ }
+
+ public void requestLocationUpdates(LocationRequest request, PendingIntent pendingIntent) {
+ Log.d(TAG,"requestLocationUpdates()");
+ Intent i = new Intent(context, NativePendingIntentForwarder.class);
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(EXTRA_PENDING_INTENT, pendingIntent);
+ i.putExtras(bundle);
+ pendingCount.put(pendingIntent, request.getNumUpdates());
+ nativePendingMap.put(pendingIntent, PendingIntent.getActivity(context, 0, i, 0));
+ locationManager.requestLocationUpdates(request.getInterval(),
+ request.getSmallestDesplacement(), makeNativeCriteria(request),
+ nativePendingMap.get(pendingIntent));
+ }
+
+ public void requestLocationUpdates(LocationRequest request, LocationListener listener, Looper
+ looper) {
+ Log.d(TAG,"requestLocationUpdates()");
+ if (nativeListenerMap.containsKey(listener)) {
+ removeLocationUpdates(listener);
+ }
+ nativeListenerMap.put(listener, new NativeListener(listener, request.getNumUpdates()));
+ locationManager.requestLocationUpdates(request.getInterval(),
+ request.getSmallestDesplacement(), makeNativeCriteria(request),
+ nativeListenerMap.get(listener), looper);
+ }
+
+ public void removeLocationUpdates(LocationListener listener) {
+ Log.d(TAG,"removeLocationUpdates()");
+ locationManager.removeUpdates(nativeListenerMap.get(listener));
+ nativeListenerMap.remove(listener);
+ }
+
+ public void removeLocationUpdates(PendingIntent pendingIntent) {
+ Log.d(TAG,"removeLocationUpdates()");
+ locationManager.removeUpdates(nativePendingMap.get(pendingIntent));
+ nativePendingMap.remove(pendingIntent);
+ pendingCount.remove(pendingIntent);
+ }
+
+ public void setMockMode(boolean isMockMode) {
+ Log.d(TAG,"setMockMode()");
+ // not yet supported
+ }
+
+ public void setMockLocation(Location mockLocation) {
+ Log.d(TAG,"setMockLocation()");
+ // not yet supported
+ }
+
+ public static class NativePendingIntentForwarder extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.hasExtra(LocationManager.KEY_LOCATION_CHANGED)) {
+ PendingIntent pendingIntent = intent.getExtras().getParcelable
+ (EXTRA_PENDING_INTENT);
+ try {
+ pendingIntent.send(context, 0, intent);
+ pendingCount.put(pendingIntent, pendingCount.get(pendingIntent) - 1);
+ if (pendingCount.get(pendingIntent) == 0) {
+ ((LocationManager) context.getSystemService(Context.LOCATION_SERVICE))
+ .removeUpdates(nativePendingMap.get(pendingIntent));
+ nativePendingMap.remove(pendingIntent);
+ pendingCount.remove(pendingIntent);
+ }
+ } catch (PendingIntent.CanceledException e) {
+ ((LocationManager) context.getSystemService(Context.LOCATION_SERVICE))
+ .removeUpdates(nativePendingMap.get(pendingIntent));
+ nativePendingMap.remove(pendingIntent);
+ pendingCount.remove(pendingIntent);
+ }
+ }
+ }
+ }
+
+ public class NativeListener implements android.location.LocationListener {
+
+ private final LocationListener listener;
+ private int count;
+
+ private NativeListener(LocationListener listener, int count) {
+ this.listener = listener;
+ this.count = count;
+ }
+
+ @Override
+ public void onLocationChanged(Location location) {
+ listener.onLocationChanged(location);
+ count--;
+ if (count == 0) {
+ locationManager.removeUpdates(this);
+ nativeListenerMap.remove(listener);
+ }
+ }
+
+ @Override
+ public void onStatusChanged(String provider, int status, Bundle extras) {
+
+ }
+
+ @Override
+ public void onProviderEnabled(String provider) {
+
+ }
+
+ @Override
+ public void onProviderDisabled(String provider) {
+
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ NativeListener that = (NativeListener) o;
+
+ if (!listener.equals(that.listener)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return listener.hashCode();
+ }
+ }
+}