diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 16e3856e..7c0e18bf 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,4 +1,6 @@ - - + + + diff --git a/src/com/google/android/gms/common/GooglePlayServicesClient.java b/src/com/google/android/gms/common/GooglePlayServicesClient.java new file mode 100644 index 00000000..9796592f --- /dev/null +++ b/src/com/google/android/gms/common/GooglePlayServicesClient.java @@ -0,0 +1,40 @@ +package com.google.android.gms.common; + +import android.os.Bundle; + +@Deprecated +public interface GooglePlayServicesClient { + void connect(); + + void disconnect(); + + boolean isConnected(); + + boolean isConnecting(); + + void registerConnectionCallbacks(ConnectionCallbacks listener); + + boolean isConnectionCallbacksRegistered(ConnectionCallbacks listener); + + void unregisterConnectionCallbacks(ConnectionCallbacks listener); + + void registerConnectionFailedListener(OnConnectionFailedListener listener); + + boolean isConnectionFailedListenerRegistered(OnConnectionFailedListener listener); + + void unregisterConnectionFailedListener(OnConnectionFailedListener listener); + + @Deprecated + public interface OnConnectionFailedListener { + + void onConnectionFailed(ConnectionResult result); + } + + @Deprecated + public interface ConnectionCallbacks { + + void onConnected(Bundle connectionHint); + + void onDisconnected(); + } +} diff --git a/src/com/google/android/gms/common/GooglePlayServicesUtil.java b/src/com/google/android/gms/common/GooglePlayServicesUtil.java index c4d83b21..a32adf04 100644 --- a/src/com/google/android/gms/common/GooglePlayServicesUtil.java +++ b/src/com/google/android/gms/common/GooglePlayServicesUtil.java @@ -49,7 +49,8 @@ public class GooglePlayServicesUtil { } public static int isGooglePlayServicesAvailable(Context context) { - return 0; // TODO + // As we can't know right now if the later desired feature is available, we just pretend it to be. + return ConnectionResult.SUCCESS; } public static boolean isGoogleSignedUid(PackageManager packageManager, int uid) { diff --git a/src/com/google/android/gms/common/api/Api.java b/src/com/google/android/gms/common/api/Api.java index 5d7e45ab..d094fdd0 100644 --- a/src/com/google/android/gms/common/api/Api.java +++ b/src/com/google/android/gms/common/api/Api.java @@ -1,5 +1,8 @@ package com.google.android.gms.common.api; +import android.content.Context; +import android.os.Looper; + /** * Describes a section of the Google Play Services API that should be made available. Instances of * this should be passed into {@link GoogleApiClient.Builder#addApi(Api)} to enable the appropriate @@ -13,6 +16,16 @@ package com.google.android.gms.common.api; * See {@link GoogleApiClient.Builder} for usage examples. */ public final class Api { + + private final Builder builder; + + public Api(Builder builder) { + this.builder = builder; + } + + public Builder getBuilder() { + return builder; + } /** * Base interface for API options. These are used to configure specific parameters for @@ -34,7 +47,7 @@ public final class Api { /** * {@link ApiOptions} implementation for {@link Api}s that do not take any options. */ - public class NoOptions implements NotRequiredOptions { + public final class NoOptions implements NotRequiredOptions { } /** @@ -43,4 +56,16 @@ public final class Api { public interface Optional extends HasOptions, NotRequiredOptions { } } + + public interface Connection { + public void connect(); + public void disconnect(); + public boolean isConnected(); + } + + public interface Builder { + Connection build(Context context, Looper looper, O options, AccountInfo accountInfo, + GoogleApiClient.ConnectionCallbacks callbacks, + GoogleApiClient.OnConnectionFailedListener connectionFailedListener); + } } diff --git a/src/com/google/android/gms/common/api/GoogleApiClient.java b/src/com/google/android/gms/common/api/GoogleApiClient.java index a6029a6d..3f024457 100644 --- a/src/com/google/android/gms/common/api/GoogleApiClient.java +++ b/src/com/google/android/gms/common/api/GoogleApiClient.java @@ -3,12 +3,19 @@ package com.google.android.gms.common.api; import android.app.Activity; import android.content.Context; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.support.v4.app.FragmentActivity; import android.view.View; import com.google.android.gms.common.ConnectionResult; +import org.microg.gms.Constants; +import org.microg.gms.common.api.GoogleApiClientImpl; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; -import java.util.logging.Handler; /** * The main entry point for Google Play services integration. @@ -209,13 +216,27 @@ public interface GoogleApiClient { * Builder to configure a {@link GoogleApiClient}. */ public class Builder { + private final Context context; + private final Map apis = new HashMap<>(); + private final Set connectionCallbacks = new HashSet<>(); + private final Set connectionFailedListeners = new HashSet<>(); + private final Set scopes = new HashSet<>(); + private String accountName; + private int clientId = -1; + private FragmentActivity fragmentActivity; + private Looper looper; + private int gravityForPopups; + private OnConnectionFailedListener unresolvedConnectionFailedListener; + private View viewForPopups; + /** * Builder to help construct the {@link GoogleApiClient} object. * * @param context The context to use for the connection. */ public Builder(Context context) { - + this.context = context; + this.looper = context.getMainLooper(); } /** @@ -242,7 +263,7 @@ public interface GoogleApiClient { * @see Api */ public Builder addApi(Api api, O options) { - // TODO + apis.put(api, options); return this; } @@ -253,7 +274,7 @@ public interface GoogleApiClient { * @see Api */ public Builder addApi(Api api) { - // TODO + apis.put(api, null); return this; } @@ -273,7 +294,7 @@ public interface GoogleApiClient { * call are delivered. */ public Builder addConnectionCallbacks(ConnectionCallbacks listener) { - // TODO + connectionCallbacks.add(listener); return this; } @@ -293,7 +314,7 @@ public interface GoogleApiClient { * call are delivered. */ public Builder addOnConnectionFailedListener(OnConnectionFailedListener listener) { - // TODO + connectionFailedListeners.add(listener); return this; } @@ -305,7 +326,7 @@ public interface GoogleApiClient { * @see com.google.android.gms.common.Scopes */ public Builder addScope(Scope scope) { - // TODO + scopes.add(scope.getScopeUri()); return this; } @@ -315,13 +336,20 @@ public interface GoogleApiClient { * @return The {@link GoogleApiClient} object. */ public GoogleApiClient build() { - return null; // TODO + return new GoogleApiClientImpl(context, looper, getAccountInfo(), apis, + connectionCallbacks, connectionFailedListeners, clientId); + } + + private AccountInfo getAccountInfo() { + return null; } public Builder enableAutoManage(FragmentActivity fragmentActivity, int cliendId, OnConnectionFailedListener unresolvedConnectionFailedListener) throws NullPointerException, IllegalArgumentException, IllegalStateException { - // TODO + this.fragmentActivity = fragmentActivity; + this.clientId = cliendId; + this.unresolvedConnectionFailedListener = unresolvedConnectionFailedListener; return this; } @@ -334,7 +362,7 @@ public interface GoogleApiClient { * {@link GoogleApiClient}. */ public Builder setAccountName(String accountName) { - // TODO + this.accountName = accountName; return this; } @@ -345,7 +373,7 @@ public interface GoogleApiClient { * @param gravityForPopups The gravity which controls the placement of games service popups. */ public Builder setGravityForPopups(int gravityForPopups) { - // TODO + this.gravityForPopups = gravityForPopups; return this; } @@ -355,7 +383,7 @@ public interface GoogleApiClient { * thread will be used. */ public Builder setHandler(Handler handler) { - // TODO + this.looper = handler.getLooper(); return this; } @@ -365,7 +393,7 @@ public interface GoogleApiClient { * @param viewForPopups The view to use as a content view for popups. View cannot be null. */ public Builder setViewForPopups(View viewForPopups) { - // TODO + this.viewForPopups = viewForPopups; return this; } @@ -373,13 +401,13 @@ public interface GoogleApiClient { * Specify that the default account should be used when connecting to services. */ public Builder useDefaultAccount() { - // TODO + this.accountName = Constants.DEFAULT_ACCOUNT; return this; } } /** - * Provides callbacks that are called when the client is connected or disconnected from the + * Provides callbacks that are called when the client is connected or disconnected from the * service. Most applications implement {@link #onConnected(Bundle)} to start making requests. */ public interface ConnectionCallbacks { diff --git a/src/com/google/android/gms/common/api/Result.java b/src/com/google/android/gms/common/api/Result.java deleted file mode 100644 index d8408112..00000000 --- a/src/com/google/android/gms/common/api/Result.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.google.android.gms.common.api; - -/** - * Represents the final result of invoking an API method in Google Play Services. - */ -public interface Result { - public Status getStatus(); -} diff --git a/src/com/google/android/gms/common/api/Status.java b/src/com/google/android/gms/common/api/Status.java deleted file mode 100644 index ae65e646..00000000 --- a/src/com/google/android/gms/common/api/Status.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.google.android.gms.common.api; - -/** - * Represents the results of work. - *

- * TODO: content (is this IPC API?) - */ -public final class Status implements Result { - @Override - public Status getStatus() { - return this; - } -} diff --git a/src/com/google/android/gms/location/FusedLocationProviderApi.java b/src/com/google/android/gms/location/FusedLocationProviderApi.java new file mode 100644 index 00000000..5d5ba084 --- /dev/null +++ b/src/com/google/android/gms/location/FusedLocationProviderApi.java @@ -0,0 +1,33 @@ +package com.google.android.gms.location; + +import android.app.PendingIntent; +import android.location.Location; +import android.os.Looper; +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.PendingResult; +import org.microg.gms.Constants; + +public interface FusedLocationProviderApi { + String KEY_LOCATION_CHANGED = "com.google.android.location.LOCATION"; + String KEY_MOCK_LOCATION = Constants.KEY_MOCK_LOCATION; + + public Location getLastLocation(GoogleApiClient client); + + public PendingResult requestLocationUpdates(GoogleApiClient client, LocationRequest request, + LocationListener listener); + + public PendingResult requestLocationUpdates(GoogleApiClient client, LocationRequest request, + LocationListener listener, Looper looper); + + public PendingResult requestLocationUpdates(GoogleApiClient client, LocationRequest request, + PendingIntent callbackIntent); + + public PendingResult removeLocationUpdates(GoogleApiClient client, LocationListener listener); + + public PendingResult removeLocationUpdates(GoogleApiClient client, + PendingIntent callbackIntent); + + public PendingResult setMockMode(GoogleApiClient client, boolean isMockMode); + + public PendingResult setMockLocation(GoogleApiClient client, Location mockLocation); +} diff --git a/src/com/google/android/gms/location/GeofencingApi.java b/src/com/google/android/gms/location/GeofencingApi.java new file mode 100644 index 00000000..fe2f8762 --- /dev/null +++ b/src/com/google/android/gms/location/GeofencingApi.java @@ -0,0 +1,4 @@ +package com.google.android.gms.location; + +public interface GeofencingApi { +} diff --git a/src/com/google/android/gms/location/LocationClient.java b/src/com/google/android/gms/location/LocationClient.java new file mode 100644 index 00000000..b3072880 --- /dev/null +++ b/src/com/google/android/gms/location/LocationClient.java @@ -0,0 +1,78 @@ +package com.google.android.gms.location; + +import android.content.Context; +import com.google.android.gms.common.GooglePlayServicesClient; +import com.google.android.gms.common.api.GoogleApiClient; +import org.microg.gms.common.ForwardConnectionCallbacks; +import org.microg.gms.common.ForwardConnectionFailedListener; + +@Deprecated +public class LocationClient implements GooglePlayServicesClient { + private GoogleApiClient googleApiClient; + + public LocationClient(Context context, ConnectionCallbacks callbacks) { + googleApiClient = new GoogleApiClient.Builder(context) + .addApi(LocationServices.API) + .addConnectionCallbacks(new ForwardConnectionCallbacks(callbacks)) + .build(); + } + + @Override + public void connect() { + googleApiClient.connect(); + } + + @Override + public void disconnect() { + googleApiClient.disconnect(); + } + + @Override + public boolean isConnected() { + return googleApiClient.isConnected(); + } + + @Override + public boolean isConnecting() { + return googleApiClient.isConnecting(); + } + + @Override + public void registerConnectionCallbacks(final ConnectionCallbacks listener) { + googleApiClient.registerConnectionCallbacks(new ForwardConnectionCallbacks(listener)); + } + + @Override + public boolean isConnectionCallbacksRegistered(ConnectionCallbacks listener) { + return googleApiClient + .isConnectionCallbacksRegistered(new ForwardConnectionCallbacks(listener)); + } + + @Override + public void unregisterConnectionCallbacks( + ConnectionCallbacks listener) { + googleApiClient.unregisterConnectionCallbacks(new ForwardConnectionCallbacks(listener)); + } + + @Override + public void registerConnectionFailedListener( + OnConnectionFailedListener listener) { + googleApiClient.registerConnectionFailedListener( + new ForwardConnectionFailedListener(listener)); + } + + @Override + public boolean isConnectionFailedListenerRegistered( + OnConnectionFailedListener listener) { + return googleApiClient.isConnectionFailedListenerRegistered( + new ForwardConnectionFailedListener(listener)); + } + + @Override + public void unregisterConnectionFailedListener( + OnConnectionFailedListener listener) { + googleApiClient.unregisterConnectionFailedListener( + new ForwardConnectionFailedListener(listener)); + } + +} diff --git a/src/com/google/android/gms/location/LocationListener.java b/src/com/google/android/gms/location/LocationListener.java new file mode 100644 index 00000000..63603d67 --- /dev/null +++ b/src/com/google/android/gms/location/LocationListener.java @@ -0,0 +1,4 @@ +package com.google.android.gms.location; + +public interface LocationListener { +} diff --git a/src/com/google/android/gms/location/LocationServices.java b/src/com/google/android/gms/location/LocationServices.java new file mode 100644 index 00000000..ced08aa6 --- /dev/null +++ b/src/com/google/android/gms/location/LocationServices.java @@ -0,0 +1,28 @@ +package com.google.android.gms.location; + +import android.content.Context; +import android.os.Looper; +import com.google.android.gms.common.api.AccountInfo; +import com.google.android.gms.common.api.Api; +import com.google.android.gms.common.api.GoogleApiClient; +import org.microg.gms.location.FusedLocationProviderApiImpl; +import org.microg.gms.location.GeofencingApiImpl; +import org.microg.gms.location.LocationClientImpl; + +/** + * The main entry point for location services integration. + */ +public class LocationServices { + public static final Api API = new Api<>( + new Api.Builder() { + @Override + public Api.Connection build(Context context, Looper looper, + Api.ApiOptions.NoOptions options, + AccountInfo accountInfo, GoogleApiClient.ConnectionCallbacks callbacks, + GoogleApiClient.OnConnectionFailedListener connectionFailedListener) { + return new LocationClientImpl(context); + } + }); + public static final FusedLocationProviderApi FusedLocationApi = new FusedLocationProviderApiImpl(); + public static final GeofencingApi GeofencingApi = new GeofencingApiImpl(); +} diff --git a/src/org/microg/gms/common/ForwardConnectionCallbacks.java b/src/org/microg/gms/common/ForwardConnectionCallbacks.java new file mode 100644 index 00000000..026b9340 --- /dev/null +++ b/src/org/microg/gms/common/ForwardConnectionCallbacks.java @@ -0,0 +1,35 @@ +package org.microg.gms.common; + +import android.os.Bundle; +import com.google.android.gms.common.GooglePlayServicesClient; +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.location.LocationClient; + +public final class ForwardConnectionCallbacks implements GoogleApiClient.ConnectionCallbacks { + private final GooglePlayServicesClient.ConnectionCallbacks callbacks; + + public ForwardConnectionCallbacks(GooglePlayServicesClient.ConnectionCallbacks callbacks) { + this.callbacks = callbacks; + } + + @Override + public boolean equals(Object o) { + return o instanceof ForwardConnectionCallbacks && + callbacks.equals(((ForwardConnectionCallbacks) o).callbacks); + } + + @Override + public int hashCode() { + return callbacks.hashCode(); + } + + @Override + public void onConnected(Bundle connectionHint) { + callbacks.onConnected(connectionHint); + } + + @Override + public void onConnectionSuspended(int cause) { + callbacks.onDisconnected(); + } +} diff --git a/src/org/microg/gms/common/ForwardConnectionFailedListener.java b/src/org/microg/gms/common/ForwardConnectionFailedListener.java new file mode 100644 index 00000000..273383f9 --- /dev/null +++ b/src/org/microg/gms/common/ForwardConnectionFailedListener.java @@ -0,0 +1,31 @@ +package org.microg.gms.common; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GooglePlayServicesClient; +import com.google.android.gms.common.api.GoogleApiClient; + +public final class ForwardConnectionFailedListener + implements GoogleApiClient.OnConnectionFailedListener { + private final GooglePlayServicesClient.OnConnectionFailedListener listener; + + public ForwardConnectionFailedListener( + GooglePlayServicesClient.OnConnectionFailedListener listener) { + this.listener = listener; + } + + @Override + public boolean equals(Object o) { + return o instanceof ForwardConnectionFailedListener && + listener.equals(((ForwardConnectionFailedListener) o).listener); + } + + @Override + public int hashCode() { + return listener.hashCode(); + } + + @Override + public void onConnectionFailed(ConnectionResult result) { + listener.onConnectionFailed(result); + } +} diff --git a/src/org/microg/gms/common/GmsClient.java b/src/org/microg/gms/common/GmsClient.java new file mode 100644 index 00000000..046c2e36 --- /dev/null +++ b/src/org/microg/gms/common/GmsClient.java @@ -0,0 +1,105 @@ +package org.microg.gms.common; + +import android.content.ComponentName; +import android.content.Context; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.os.IInterface; +import android.os.RemoteException; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GooglePlayServicesUtil; +import com.google.android.gms.common.api.Api; +import com.google.android.gms.common.internal.IGmsCallbacks; +import com.google.android.gms.common.internal.IGmsServiceBroker; + +public abstract class GmsClient implements Api.Connection { + private static final String TAG = "GmsClient"; + + private final Context context; + private ConnectionState state = ConnectionState.CONNECTED; + private ServiceConnection serviceConnection; + private I serviceInterface; + + public GmsClient(Context context) { + this.context = context; + } + + protected abstract String getActionString(); + + protected abstract void onConnectedToBroker(IGmsServiceBroker broker, GmsCallbacks callbacks) + throws RemoteException; + + protected abstract I interfaceFromBinder(IBinder binder); + + @Override + public void connect() { + 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); + } + } + + @Override + public void disconnect() { + serviceInterface = null; + if (serviceConnection != null) { + MultiConnectionKeeper.getInstance(context).unbind(getActionString(), serviceConnection); + serviceConnection = null; + } + state = ConnectionState.NOT_CONNECTED; + } + + @Override + public boolean isConnected() { + return state == ConnectionState.CONNECTED; + } + + public Context getContext() { + return context; + } + + public I getServiceInterface() { + return serviceInterface; + } + + private enum ConnectionState { + NOT_CONNECTED, CONNECTING, CONNECTED, ERROR + } + + private class GmsServiceConnection implements ServiceConnection { + + @Override + public void onServiceConnected(ComponentName componentName, IBinder iBinder) { + try { + onConnectedToBroker(IGmsServiceBroker.Stub.asInterface(iBinder), new GmsCallbacks()); + } catch (RemoteException e) { + disconnect(); + } + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + state = ConnectionState.ERROR; + } + } + + public class GmsCallbacks extends IGmsCallbacks.Stub { + + @Override + public void onPostInitComplete(int statusCode, IBinder binder, Bundle params) + throws RemoteException { + serviceInterface = interfaceFromBinder(binder); + } + } + +} diff --git a/src/org/microg/gms/common/MultiConnectionKeeper.java b/src/org/microg/gms/common/MultiConnectionKeeper.java new file mode 100644 index 00000000..459b9ccd --- /dev/null +++ b/src/org/microg/gms/common/MultiConnectionKeeper.java @@ -0,0 +1,136 @@ +package org.microg.gms.common; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static org.microg.gms.Constants.GMS_PACKAGE_NAME; + +public class MultiConnectionKeeper { + private static MultiConnectionKeeper INSTANCE; + + private final Context context; + private final Map connections = new HashMap<>(); + + public MultiConnectionKeeper(Context context) { + this.context = context; + } + + public static MultiConnectionKeeper getInstance(Context context) { + if (INSTANCE == null) + INSTANCE = new MultiConnectionKeeper(context); + return INSTANCE; + } + + public boolean bind(String action, ServiceConnection connection) { + Connection con = connections.get(action); + if (con != null) { + if (!con.forwardsConnection(connection)) { + con.addConnectionForward(connection); + if (!con.isBound()) + con.bind(); + } + } else { + con = new Connection(action); + con.addConnectionForward(connection); + con.bind(); + connections.put(action, con); + } + return con.isBound(); + } + + public void unbind(String action, ServiceConnection connection) { + Connection con = connections.get(action); + if (con != null) { + con.removeConnectionForward(connection); + if (!con.hasForwards() && con.isBound()) { + con.unbind(); + } + } + } + + public class Connection { + private final String actionString; + private final Set connectionForwards = new HashSet<>(); + private boolean bound = false; + private boolean connected = false; + private IBinder binder; + private ComponentName component; + private ServiceConnection serviceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName componentName, IBinder iBinder) { + binder = iBinder; + component = componentName; + for (ServiceConnection connection : connectionForwards) { + connection.onServiceConnected(componentName, iBinder); + } + connected = true; + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + binder = null; + component = componentName; + for (ServiceConnection connection : connectionForwards) { + connection.onServiceDisconnected(componentName); + } + connected = false; + } + }; + + public Connection(String actionString) { + this.actionString = actionString; + } + + public void bind() { + Intent intent = new Intent(actionString).setPackage(GMS_PACKAGE_NAME); + bound = context.bindService(intent, serviceConnection, + Context.BIND_ADJUST_WITH_ACTIVITY & Context.BIND_AUTO_CREATE); + if (!bound) { + context.unbindService(serviceConnection); + } + } + + public boolean isBound() { + return bound; + } + + public IBinder getBinder() { + return binder; + } + + public void unbind() { + context.unbindService(serviceConnection); + bound = false; + } + + public void addConnectionForward(ServiceConnection connection) { + connectionForwards.add(connection); + if (connected) { + connection.onServiceConnected(component, binder); + } + } + + public void removeConnectionForward(ServiceConnection connection) { + connectionForwards.remove(connection); + if (connected) { + connection.onServiceDisconnected(component); + } + } + + public boolean forwardsConnection(ServiceConnection connection) { + return connectionForwards.contains(connection); + } + + public boolean hasForwards() { + return !connectionForwards.isEmpty(); + } + } +} diff --git a/src/org/microg/gms/common/api/AbstractPendingResult.java b/src/org/microg/gms/common/api/AbstractPendingResult.java new file mode 100644 index 00000000..dfb00a86 --- /dev/null +++ b/src/org/microg/gms/common/api/AbstractPendingResult.java @@ -0,0 +1,95 @@ +package org.microg.gms.common.api; + +import android.os.Looper; +import com.google.android.gms.common.api.PendingResult; +import com.google.android.gms.common.api.Result; +import com.google.android.gms.common.api.ResultCallback; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class AbstractPendingResult implements PendingResult { + private final Object lock = new Object(); + private final CountDownLatch countDownLatch = new CountDownLatch(1); + private final CallbackHandler handler; + private boolean canceled; + private R result; + private ResultCallback resultCallback; + + public AbstractPendingResult(Looper looper) { + handler = new CallbackHandler(looper); + } + + private R getResult() { + synchronized (lock) { + return result; + } + } + + @Override + public R await() { + try { + countDownLatch.await(); + } catch (InterruptedException ignored) { + } + return getResult(); + } + + @Override + public R await(long time, TimeUnit unit) { + try { + countDownLatch.await(time, unit); + } catch (InterruptedException ignored) { + } + return getResult(); + } + + @Override + public void cancel() { + // TODO + } + + @Override + public boolean isCanceled() { + synchronized (lock) { + return canceled; + } + } + + public boolean isReady() { + return this.countDownLatch.getCount() == 0L; + } + + @Override + public void setResultCallback(ResultCallback callback, long time, TimeUnit unit) { + synchronized (lock) { + if (!isCanceled()) { + if (isReady()) { + handler.sendResultCallback(callback, getResult()); + } else { + handler.sendTimeoutResultCallback(this, unit.toMillis(time)); + } + } + } + } + + @Override + public void setResultCallback(ResultCallback callback) { + synchronized (lock) { + if (!isCanceled()) { + if (isReady()) { + handler.sendResultCallback(callback, getResult()); + } else { + resultCallback = callback; + } + } + } + } + + private void deliverResult(R result) { + this.result = result; + countDownLatch.countDown(); + + } + +} diff --git a/src/org/microg/gms/common/api/CallbackHandler.java b/src/org/microg/gms/common/api/CallbackHandler.java new file mode 100644 index 00000000..9d573abc --- /dev/null +++ b/src/org/microg/gms/common/api/CallbackHandler.java @@ -0,0 +1,42 @@ +package org.microg.gms.common.api; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import com.google.android.gms.common.api.Result; +import com.google.android.gms.common.api.ResultCallback; + +class CallbackHandler extends Handler { + public static final int CALLBACK_ON_COMPLETE = 1; + public static final int CALLBACK_ON_TIMEOUT = 2; + + public CallbackHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case CALLBACK_ON_COMPLETE: + OnCompleteObject o = (OnCompleteObject) msg.obj; + o.callback.onResult(o.result); + break; + case CALLBACK_ON_TIMEOUT: + // TODO + break; + } + } + + public void sendResultCallback(ResultCallback callback, R result) { + + } + + public void sendTimeoutResultCallback(AbstractPendingResult pendingResult, long millis) { + + } + + public static class OnCompleteObject { + public ResultCallback callback; + public R result; + } +} diff --git a/src/org/microg/gms/common/api/GoogleApiClientImpl.java b/src/org/microg/gms/common/api/GoogleApiClientImpl.java new file mode 100644 index 00000000..f77b5243 --- /dev/null +++ b/src/org/microg/gms/common/api/GoogleApiClientImpl.java @@ -0,0 +1,155 @@ +package org.microg.gms.common.api; + +import android.content.Context; +import android.os.Bundle; +import android.os.Looper; +import android.support.v4.app.FragmentActivity; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.api.*; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +public class GoogleApiClientImpl implements GoogleApiClient { + private final Context context; + private final Looper looper; + private final AccountInfo accountInfo; + private final Map apis = new HashMap<>(); + private final Map apiConnections = new HashMap<>(); + private final Set connectionCallbacks = new HashSet<>(); + private final Set connectionFailedListeners = new HashSet<>(); + private final ConnectionCallbacks baseConnectionCallbacks = new ConnectionCallbacks() { + @Override + public void onConnected(Bundle connectionHint) { + for (ConnectionCallbacks callback : connectionCallbacks) { + callback.onConnected(connectionHint); + } + } + + @Override + public void onConnectionSuspended(int cause) { + for (ConnectionCallbacks callback : connectionCallbacks) { + callback.onConnectionSuspended(cause); + } + } + }; + private final OnConnectionFailedListener baseConnectionFailedListener = new OnConnectionFailedListener() { + @Override + public void onConnectionFailed(ConnectionResult result) { + for (OnConnectionFailedListener listener : connectionFailedListeners) { + listener.onConnectionFailed(result); + } + } + }; + private final int clientId; + + public GoogleApiClientImpl(Context context, Looper looper, AccountInfo accountInfo, + Map apis, + Set connectionCallbacks, + Set connectionFailedListeners, int clientId) { + this.context = context; + this.looper = looper; + this.accountInfo = accountInfo; + this.apis.putAll(apis); + this.connectionCallbacks.addAll(connectionCallbacks); + this.connectionFailedListeners.addAll(connectionFailedListeners); + this.clientId = clientId; + + for (Api api : apis.keySet()) { + apiConnections.put(api, api.getBuilder().build(context, looper, + apis.get(api), accountInfo, baseConnectionCallbacks, + baseConnectionFailedListener)); + } + } + + public Api.Connection getApiConnection(Api api) { + return apiConnections.get(api); + } + + @Override + public ConnectionResult blockingConnect() { + return null; + } + + @Override + public ConnectionResult blockingConnect(long timeout, TimeUnit unit) { + return null; + } + + @Override + public PendingResult clearDefaultAccountAndReconnect() { + return null; + } + + @Override + public void connect() { + for (Api.Connection connection : apiConnections.values()) { + connection.connect(); + } + } + + @Override + public void disconnect() { + for (Api.Connection connection : apiConnections.values()) { + connection.disconnect(); + } + } + + @Override + public boolean isConnected() { + for (Api.Connection connection : apiConnections.values()) { + if (!connection.isConnected()) return false; + } + return true; + } + + @Override + public boolean isConnecting() { + return false; // TODO + } + + @Override + public boolean isConnectionCallbacksRegistered(ConnectionCallbacks listener) { + return connectionCallbacks.contains(listener); + } + + @Override + public boolean isConnectionFailedListenerRegistered( + OnConnectionFailedListener listener) { + return connectionFailedListeners.contains(listener); + } + + @Override + public void reconnect() { + disconnect(); + connect(); + } + + @Override + public void registerConnectionCallbacks(ConnectionCallbacks listener) { + connectionCallbacks.add(listener); + } + + @Override + public void registerConnectionFailedListener(OnConnectionFailedListener listener) { + connectionFailedListeners.add(listener); + } + + @Override + public void stopAutoManager(FragmentActivity lifecycleActivity) throws IllegalStateException { + + } + + @Override + public void unregisterConnectionCallbacks(ConnectionCallbacks listener) { + connectionCallbacks.remove(listener); + } + + @Override + public void unregisterConnectionFailedListener(OnConnectionFailedListener listener) { + connectionFailedListeners.remove(listener); + } +} diff --git a/src/org/microg/gms/location/FusedLocationProviderApiImpl.java b/src/org/microg/gms/location/FusedLocationProviderApiImpl.java new file mode 100644 index 00000000..eb4c516f --- /dev/null +++ b/src/org/microg/gms/location/FusedLocationProviderApiImpl.java @@ -0,0 +1,72 @@ +package org.microg.gms.location; + +import android.app.PendingIntent; +import android.location.Location; +import android.os.Looper; +import android.os.RemoteException; +import android.util.Log; +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.PendingResult; +import com.google.android.gms.location.FusedLocationProviderApi; +import com.google.android.gms.location.LocationListener; +import com.google.android.gms.location.LocationRequest; + +public class FusedLocationProviderApiImpl implements FusedLocationProviderApi { + private static final String TAG = "GmsFusedApiImpl"; + + @Override + public Location getLastLocation(GoogleApiClient client) { + try { + return LocationClientImpl.get(client).getLastLocation(); + } catch (RemoteException e) { + Log.w(TAG, e); + return null; + } + } + + @Override + public PendingResult requestLocationUpdates(GoogleApiClient client, LocationRequest request, + LocationListener listener) { + //LocationClientImpl.get(client).requestLocationUpdates(request, listener); + return null; + } + + @Override + public PendingResult requestLocationUpdates(GoogleApiClient client, LocationRequest request, + LocationListener listener, Looper looper) { + //LocationClientImpl.get(client).requestLocationUpdates(request, listener, looper); + return null; + } + + @Override + public PendingResult requestLocationUpdates(GoogleApiClient client, LocationRequest request, + PendingIntent callbackIntent) { + //LocationClientImpl.get(client).requestLocationUpdates(request, callbackIntent); + return null; + } + + @Override + public PendingResult removeLocationUpdates(GoogleApiClient client, LocationListener listener) { + //LocationClientImpl.get(client).removeLocationUpdates(listener); + return null; + } + + @Override + public PendingResult removeLocationUpdates(GoogleApiClient client, + PendingIntent callbackIntent) { + //LocationClientImpl.get(client).removeLocationUpdates(callbackIntent); + return null; + } + + @Override + public PendingResult setMockMode(GoogleApiClient client, boolean isMockMode) { + //LocationClientImpl.get(client).setMockMode(isMockMode); + return null; + } + + @Override + public PendingResult setMockLocation(GoogleApiClient client, Location mockLocation) { + //LocationClientImpl.get(client).setMockLocation(mockLocation); + return null; + } +} diff --git a/src/org/microg/gms/location/GeofencingApiImpl.java b/src/org/microg/gms/location/GeofencingApiImpl.java new file mode 100644 index 00000000..27cef98c --- /dev/null +++ b/src/org/microg/gms/location/GeofencingApiImpl.java @@ -0,0 +1,6 @@ +package org.microg.gms.location; + +import com.google.android.gms.location.GeofencingApi; + +public class GeofencingApiImpl implements GeofencingApi { +} diff --git a/src/org/microg/gms/location/GoogleLocationManagerClient.java b/src/org/microg/gms/location/GoogleLocationManagerClient.java new file mode 100644 index 00000000..a60a665b --- /dev/null +++ b/src/org/microg/gms/location/GoogleLocationManagerClient.java @@ -0,0 +1,35 @@ +package org.microg.gms.location; + +import android.content.Context; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import com.google.android.gms.common.internal.IGmsServiceBroker; +import com.google.android.gms.location.internal.IGoogleLocationManagerService; +import org.microg.gms.Constants; +import org.microg.gms.common.GmsClient; + +public class GoogleLocationManagerClient extends GmsClient { + public GoogleLocationManagerClient(Context context) { + super(context); + } + + @Override + protected String getActionString() { + return Constants.ACTION_GMS_LOCATION_MANAGER_SERVICE_START; + } + + @Override + protected IGoogleLocationManagerService interfaceFromBinder(IBinder binder) { + return IGoogleLocationManagerService.Stub.asInterface(binder); + } + + @Override + protected void onConnectedToBroker(IGmsServiceBroker broker, GmsCallbacks callbacks) + throws RemoteException { + Bundle bundle = new Bundle(); + bundle.putString("client_name", "locationServices"); + broker.getGoogleLocationManagerService(callbacks, Constants.MAX_REFERENCE_VERSION, + getContext().getPackageName(), bundle); + } +} diff --git a/src/org/microg/gms/location/LocationClientImpl.java b/src/org/microg/gms/location/LocationClientImpl.java new file mode 100644 index 00000000..e523c14c --- /dev/null +++ b/src/org/microg/gms/location/LocationClientImpl.java @@ -0,0 +1,61 @@ +package org.microg.gms.location; + +import android.app.PendingIntent; +import android.content.Context; +import android.location.Location; +import android.os.Looper; +import android.os.RemoteException; +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.location.LocationListener; +import com.google.android.gms.location.LocationRequest; +import com.google.android.gms.location.LocationServices; +import org.microg.gms.common.api.GoogleApiClientImpl; + +public class LocationClientImpl extends GoogleLocationManagerClient { + public LocationClientImpl(Context context) { + super(context); + } + + public static LocationClientImpl get(GoogleApiClient apiClient) { + if (apiClient instanceof GoogleApiClientImpl) { + return (LocationClientImpl) ((GoogleApiClientImpl) apiClient) + .getApiConnection(LocationServices.API); + } + return null; + } + + public Location getLastLocation() throws RemoteException { + return getServiceInterface().getLastLocation(); + } + + public void requestLocationUpdates(LocationRequest request, LocationListener listener) + throws RemoteException { + + } + + public void requestLocationUpdates(LocationRequest request, PendingIntent pendingIntent) + throws RemoteException { + + } + + public void requestLocationUpdates(LocationRequest request, LocationListener listener, + Looper looper) throws RemoteException { + + } + + public void removeLocationUpdates(LocationListener listener) throws RemoteException { + + } + + public void removeLocationUpdates(PendingIntent pendingIntent) throws RemoteException { + + } + + public void setMockMode(boolean isMockMode) throws RemoteException { + + } + + public void setMockLocation(Location mockLocation) throws RemoteException { + + } +}