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(); + } + } +}