Merge "Add support for GPS measurement/navigation message capabilities. b/16727892 b/16815124" into lmp-mr1-dev

This commit is contained in:
destradaa
2014-11-06 23:22:41 +00:00
committed by Android (Google) Code Review
15 changed files with 459 additions and 154 deletions

View File

@@ -26,14 +26,12 @@ import android.os.RemoteException;
*/
class GpsMeasurementListenerTransport
extends LocalListenerHelper<GpsMeasurementsEvent.Listener> {
private final Context mContext;
private final ILocationManager mLocationManager;
private final IGpsMeasurementsListener mListenerTransport = new ListenerTransport();
public GpsMeasurementListenerTransport(Context context, ILocationManager locationManager) {
super("GpsMeasurementListenerTransport");
mContext = context;
super(context, "GpsMeasurementListenerTransport");
mLocationManager = locationManager;
}
@@ -41,7 +39,7 @@ class GpsMeasurementListenerTransport
protected boolean registerWithServer() throws RemoteException {
return mLocationManager.addGpsMeasurementsListener(
mListenerTransport,
mContext.getPackageName());
getContext().getPackageName());
}
@Override
@@ -59,7 +57,18 @@ class GpsMeasurementListenerTransport
listener.onGpsMeasurementsReceived(event);
}
};
foreach(operation);
}
@Override
public void onStatusChanged(final int status) {
ListenerOperation<GpsMeasurementsEvent.Listener> operation =
new ListenerOperation<GpsMeasurementsEvent.Listener>() {
@Override
public void execute(GpsMeasurementsEvent.Listener listener) throws RemoteException {
listener.onStatusChanged(status);
}
};
foreach(operation);
}
}

View File

@@ -32,6 +32,24 @@ import java.util.Collections;
* @hide
*/
public class GpsMeasurementsEvent implements Parcelable {
/**
* The system does not support tracking of GPS Measurements. This status will not change in the
* future.
*/
public static final int STATUS_NOT_SUPPORTED = 0;
/**
* GPS Measurements are successfully being tracked, it will receive updates once they are
* available.
*/
public static final int STATUS_READY = 1;
/**
* GPS provider or Location is disabled, updates will not be received until they are enabled.
*/
public static final int STATUS_GPS_LOCATION_DISABLED = 2;
private final GpsClock mClock;
private final Collection<GpsMeasurement> mReadOnlyMeasurements;
@@ -43,7 +61,16 @@ public class GpsMeasurementsEvent implements Parcelable {
* @hide
*/
public interface Listener {
/**
* Returns the latest collected GPS Measurements.
*/
void onGpsMeasurementsReceived(GpsMeasurementsEvent eventArgs);
/**
* Returns the latest status of the GPS Measurements sub-system.
*/
void onStatusChanged(int status);
}
public GpsMeasurementsEvent(GpsClock clock, GpsMeasurement[] measurements) {
@@ -103,7 +130,9 @@ public class GpsMeasurementsEvent implements Parcelable {
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeParcelable(mClock, flags);
GpsMeasurement[] measurementsArray = mReadOnlyMeasurements.toArray(new GpsMeasurement[0]);
int measurementsCount = mReadOnlyMeasurements.size();
GpsMeasurement[] measurementsArray =
mReadOnlyMeasurements.toArray(new GpsMeasurement[measurementsCount]);
parcel.writeInt(measurementsArray.length);
parcel.writeTypedArray(measurementsArray, flags);
}

View File

@@ -21,9 +21,6 @@ import android.os.Parcel;
import android.os.Parcelable;
import java.security.InvalidParameterException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
/**
* A class implementing a container for data associated with a navigation message event.
@@ -32,6 +29,24 @@ import java.util.Collections;
* @hide
*/
public class GpsNavigationMessageEvent implements Parcelable {
/**
* The system does not support tracking of GPS Navigation Messages. This status will not change
* in the future.
*/
public static int STATUS_NOT_SUPPORTED = 0;
/**
* GPS Navigation Messages are successfully being tracked, it will receive updates once they are
* available.
*/
public static int STATUS_READY = 1;
/**
* GPS provider or Location is disabled, updated will not be received until they are enabled.
*/
public static int STATUS_GPS_LOCATION_DISABLED = 2;
private final GpsNavigationMessage mNavigationMessage;
/**
@@ -42,7 +57,16 @@ public class GpsNavigationMessageEvent implements Parcelable {
* @hide
*/
public interface Listener {
/**
* Returns the latest collected GPS Navigation Message.
*/
void onGpsNavigationMessageReceived(GpsNavigationMessageEvent event);
/**
* Returns the latest status of the GPS Navigation Messages sub-system.
*/
void onStatusChanged(int status);
}
public GpsNavigationMessageEvent(GpsNavigationMessage message) {

View File

@@ -26,7 +26,6 @@ import android.os.RemoteException;
*/
class GpsNavigationMessageListenerTransport
extends LocalListenerHelper<GpsNavigationMessageEvent.Listener> {
private final Context mContext;
private final ILocationManager mLocationManager;
private final IGpsNavigationMessageListener mListenerTransport = new ListenerTransport();
@@ -34,8 +33,7 @@ class GpsNavigationMessageListenerTransport
public GpsNavigationMessageListenerTransport(
Context context,
ILocationManager locationManager) {
super("GpsNavigationMessageListenerTransport");
mContext = context;
super(context, "GpsNavigationMessageListenerTransport");
mLocationManager = locationManager;
}
@@ -43,7 +41,7 @@ class GpsNavigationMessageListenerTransport
protected boolean registerWithServer() throws RemoteException {
return mLocationManager.addGpsNavigationMessageListener(
mListenerTransport,
mContext.getPackageName());
getContext().getPackageName());
}
@Override
@@ -62,7 +60,19 @@ class GpsNavigationMessageListenerTransport
listener.onGpsNavigationMessageReceived(event);
}
};
foreach(operation);
}
@Override
public void onStatusChanged(final int status) {
ListenerOperation<GpsNavigationMessageEvent.Listener> operation =
new ListenerOperation<GpsNavigationMessageEvent.Listener>() {
@Override
public void execute(GpsNavigationMessageEvent.Listener listener)
throws RemoteException {
listener.onStatusChanged(status);
}
};
foreach(operation);
}
}

View File

@@ -23,4 +23,5 @@ import android.location.GpsMeasurementsEvent;
*/
oneway interface IGpsMeasurementsListener {
void onGpsMeasurementsReceived(in GpsMeasurementsEvent event);
void onStatusChanged(in int status);
}

View File

@@ -23,4 +23,5 @@ import android.location.GpsNavigationMessageEvent;
*/
oneway interface IGpsNavigationMessageListener {
void onGpsNavigationMessageReceived(in GpsNavigationMessageEvent event);
void onStatusChanged(in int status);
}

View File

@@ -62,12 +62,12 @@ interface ILocationManager
boolean sendNiResponse(int notifId, int userResponse);
boolean addGpsMeasurementsListener(in IGpsMeasurementsListener listener, in String packageName);
boolean removeGpsMeasurementsListener(in IGpsMeasurementsListener listener);
void removeGpsMeasurementsListener(in IGpsMeasurementsListener listener);
boolean addGpsNavigationMessageListener(
in IGpsNavigationMessageListener listener,
in String packageName);
boolean removeGpsNavigationMessageListener(in IGpsNavigationMessageListener listener);
void removeGpsNavigationMessageListener(in IGpsNavigationMessageListener listener);
// --- deprecated ---
List<String> getAllProviders();

View File

@@ -19,6 +19,7 @@ package android.location;
import com.android.internal.util.Preconditions;
import android.annotation.NonNull;
import android.content.Context;
import android.os.RemoteException;
import android.util.Log;
@@ -32,17 +33,19 @@ import java.util.HashSet;
* @hide
*/
abstract class LocalListenerHelper<TListener> {
private final HashSet<TListener> mListeners = new HashSet<TListener>();
private final String mTag;
private final HashSet<TListener> mListeners = new HashSet<>();
protected LocalListenerHelper(String name) {
private final String mTag;
private final Context mContext;
protected LocalListenerHelper(Context context, String name) {
Preconditions.checkNotNull(name);
mContext = context;
mTag = name;
}
public boolean add(@NonNull TListener listener) {
Preconditions.checkNotNull(listener);
synchronized (mListeners) {
// we need to register with the service first, because we need to find out if the
// service will actually support the request before we attempt anything
@@ -59,18 +62,15 @@ abstract class LocalListenerHelper<TListener> {
return false;
}
}
if (mListeners.contains(listener)) {
return true;
}
mListeners.add(listener);
return mListeners.add(listener);
}
return true;
}
public void remove(@NonNull TListener listener) {
Preconditions.checkNotNull(listener);
synchronized (mListeners) {
boolean removed = mListeners.remove(listener);
boolean isLastRemoved = removed && mListeners.isEmpty();
@@ -78,7 +78,7 @@ abstract class LocalListenerHelper<TListener> {
try {
unregisterFromServer();
} catch (RemoteException e) {
Log.v(mTag, "Error handling last listener removal", e);
}
}
}
@@ -91,12 +91,15 @@ abstract class LocalListenerHelper<TListener> {
void execute(TListener listener) throws RemoteException;
}
protected void foreach(ListenerOperation operation) {
protected Context getContext() {
return mContext;
}
protected void foreach(ListenerOperation<TListener> operation) {
Collection<TListener> listeners;
synchronized (mListeners) {
listeners = new ArrayList<TListener>(mListeners);
listeners = new ArrayList<>(mListeners);
}
for (TListener listener : listeners) {
try {
operation.execute(listener);

View File

@@ -1579,7 +1579,7 @@ public class LocationManager {
* Adds a GPS Measurement listener.
*
* @param listener a {@link GpsMeasurementsEvent.Listener} object to register.
* @return {@code true} if the listener was successfully registered, {@code false} otherwise.
* @return {@code true} if the listener was added successfully, {@code false} otherwise.
*
* @hide
*/
@@ -1602,7 +1602,7 @@ public class LocationManager {
* Adds a GPS Navigation Message listener.
*
* @param listener a {@link GpsNavigationMessageEvent.Listener} object to register.
* @return {@code true} if the listener was successfully registered, {@code false} otherwise.
* @return {@code true} if the listener was added successfully, {@code false} otherwise.
*
* @hide
*/

View File

@@ -60,6 +60,8 @@ import android.location.Address;
import android.location.Criteria;
import android.location.GeocoderParams;
import android.location.Geofence;
import android.location.GpsMeasurementsEvent;
import android.location.GpsNavigationMessageEvent;
import android.location.IGpsMeasurementsListener;
import android.location.IGpsNavigationMessageListener;
import android.location.IGpsStatusListener;
@@ -1859,8 +1861,8 @@ public class LocationManagerService extends ILocationManager.Stub {
}
@Override
public boolean removeGpsMeasurementsListener(IGpsMeasurementsListener listener) {
return mGpsMeasurementsProvider.removeListener(listener);
public void removeGpsMeasurementsListener(IGpsMeasurementsListener listener) {
mGpsMeasurementsProvider.removeListener(listener);
}
@Override
@@ -1888,8 +1890,8 @@ public class LocationManagerService extends ILocationManager.Stub {
}
@Override
public boolean removeGpsNavigationMessageListener(IGpsNavigationMessageListener listener) {
return mGpsNavigationMessageProvider.removeListener(listener);
public void removeGpsNavigationMessageListener(IGpsNavigationMessageListener listener) {
mGpsNavigationMessageProvider.removeListener(listener);
}
@Override

View File

@@ -162,6 +162,9 @@ public class GpsLocationProvider implements LocationProviderInterface {
private static final int GPS_CAPABILITY_MSA = 0x0000004;
private static final int GPS_CAPABILITY_SINGLE_SHOT = 0x0000008;
private static final int GPS_CAPABILITY_ON_DEMAND_TIME = 0x0000010;
private static final int GPS_CAPABILITY_GEOFENCING = 0x0000020;
private static final int GPS_CAPABILITY_MEASUREMENTS = 0x0000040;
private static final int GPS_CAPABILITY_NAV_MESSAGES = 0x0000080;
// The AGPS SUPL mode
private static final int AGPS_SUPL_MODE_MSA = 0x02;
@@ -348,20 +351,9 @@ public class GpsLocationProvider implements LocationProviderInterface {
private final ILocationManager mILocationManager;
private Location mLocation = new Location(LocationManager.GPS_PROVIDER);
private Bundle mLocationExtras = new Bundle();
private GpsStatusListenerHelper mListenerHelper = new GpsStatusListenerHelper() {
@Override
protected boolean isSupported() {
return GpsLocationProvider.isSupported();
}
@Override
protected boolean registerWithService() {
return true;
}
@Override
protected void unregisterFromService() {}
};
private final GpsStatusListenerHelper mListenerHelper;
private final GpsMeasurementsProvider mGpsMeasurementsProvider;
private final GpsNavigationMessageProvider mGpsNavigationMessageProvider;
// Handler for processing events
private Handler mHandler;
@@ -409,41 +401,6 @@ public class GpsLocationProvider implements LocationProviderInterface {
}
};
private final GpsMeasurementsProvider mGpsMeasurementsProvider = new GpsMeasurementsProvider() {
@Override
public boolean isSupported() {
return native_is_measurement_supported();
}
@Override
protected boolean registerWithService() {
return native_start_measurement_collection();
}
@Override
protected void unregisterFromService() {
native_stop_measurement_collection();
}
};
private final GpsNavigationMessageProvider mGpsNavigationMessageProvider =
new GpsNavigationMessageProvider() {
@Override
protected boolean isSupported() {
return native_is_navigation_message_supported();
}
@Override
protected boolean registerWithService() {
return native_start_navigation_message_collection();
}
@Override
protected void unregisterFromService() {
native_stop_navigation_message_collection();
}
};
public IGpsStatusProvider getGpsStatusProvider() {
return mGpsStatusProvider;
}
@@ -696,6 +653,62 @@ public class GpsLocationProvider implements LocationProviderInterface {
mHandler.getLooper());
}
});
mListenerHelper = new GpsStatusListenerHelper(mHandler) {
@Override
protected boolean isAvailableInPlatform() {
return GpsLocationProvider.isSupported();
}
@Override
protected boolean isGpsEnabled() {
return isEnabled();
}
};
mGpsMeasurementsProvider = new GpsMeasurementsProvider(mHandler) {
@Override
public boolean isAvailableInPlatform() {
return native_is_measurement_supported();
}
@Override
protected boolean registerWithService() {
return native_start_measurement_collection();
}
@Override
protected void unregisterFromService() {
native_stop_measurement_collection();
}
@Override
protected boolean isGpsEnabled() {
return isEnabled();
}
};
mGpsNavigationMessageProvider = new GpsNavigationMessageProvider(mHandler) {
@Override
protected boolean isAvailableInPlatform() {
return native_is_navigation_message_supported();
}
@Override
protected boolean registerWithService() {
return native_start_navigation_message_collection();
}
@Override
protected void unregisterFromService() {
native_stop_navigation_message_collection();
}
@Override
protected boolean isGpsEnabled() {
return isEnabled();
}
};
}
private void listenForBroadcasts() {
@@ -1445,7 +1458,9 @@ public class GpsLocationProvider implements LocationProviderInterface {
}
if (wasNavigating != mNavigating) {
mListenerHelper.onStatusChanged(mNavigating);
mListenerHelper.onGpsEnabledChanged(mNavigating);
mGpsMeasurementsProvider.onGpsEnabledChanged(mNavigating);
mGpsNavigationMessageProvider.onGpsEnabledChanged(mNavigating);
// send an intent to notify that the GPS has been enabled or disabled
Intent intent = new Intent(LocationManager.GPS_ENABLED_CHANGE_ACTION);
@@ -1598,6 +1613,11 @@ public class GpsLocationProvider implements LocationProviderInterface {
mPeriodicTimeInjection = true;
requestUtcTime();
}
mGpsMeasurementsProvider.onCapabilitiesUpdated(
(capabilities & GPS_CAPABILITY_MEASUREMENTS) == GPS_CAPABILITY_MEASUREMENTS);
mGpsNavigationMessageProvider.onCapabilitiesUpdated(
(capabilities & GPS_CAPABILITY_NAV_MESSAGES) == GPS_CAPABILITY_NAV_MESSAGES);
}
/**

View File

@@ -18,7 +18,9 @@ package com.android.server.location;
import android.location.GpsMeasurementsEvent;
import android.location.IGpsMeasurementsListener;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
/**
* An base implementation for GPS measurements provider.
@@ -29,8 +31,10 @@ import android.os.RemoteException;
*/
public abstract class GpsMeasurementsProvider
extends RemoteListenerHelper<IGpsMeasurementsListener> {
public GpsMeasurementsProvider() {
super("GpsMeasurementsProvider");
private static final String TAG = "GpsMeasurementsProvider";
public GpsMeasurementsProvider(Handler handler) {
super(handler, TAG);
}
public void onMeasurementsAvailable(final GpsMeasurementsEvent event) {
@@ -41,7 +45,56 @@ public abstract class GpsMeasurementsProvider
listener.onGpsMeasurementsReceived(event);
}
};
foreach(operation);
}
public void onCapabilitiesUpdated(boolean isGpsMeasurementsSupported) {
int status = isGpsMeasurementsSupported ?
GpsMeasurementsEvent.STATUS_READY :
GpsMeasurementsEvent.STATUS_NOT_SUPPORTED;
setSupported(isGpsMeasurementsSupported, new StatusChangedOperation(status));
}
@Override
protected ListenerOperation<IGpsMeasurementsListener> getHandlerOperation(int result) {
final int status;
switch (result) {
case RESULT_SUCCESS:
status = GpsMeasurementsEvent.STATUS_READY;
break;
case RESULT_NOT_AVAILABLE:
case RESULT_NOT_SUPPORTED:
case RESULT_INTERNAL_ERROR:
status = GpsMeasurementsEvent.STATUS_NOT_SUPPORTED;
break;
case RESULT_GPS_LOCATION_DISABLED:
status = GpsMeasurementsEvent.STATUS_GPS_LOCATION_DISABLED;
break;
default:
Log.v(TAG, "Unhandled addListener result: " + result);
return null;
}
return new StatusChangedOperation(status);
}
@Override
protected void handleGpsEnabledChanged(boolean enabled) {
int status = enabled ?
GpsMeasurementsEvent.STATUS_READY :
GpsMeasurementsEvent.STATUS_GPS_LOCATION_DISABLED;
foreach(new StatusChangedOperation(status));
}
private class StatusChangedOperation implements ListenerOperation<IGpsMeasurementsListener> {
private final int mStatus;
public StatusChangedOperation(int status) {
mStatus = status;
}
@Override
public void execute(IGpsMeasurementsListener listener) throws RemoteException {
listener.onStatusChanged(mStatus);
}
}
}

View File

@@ -18,7 +18,9 @@ package com.android.server.location;
import android.location.GpsNavigationMessageEvent;
import android.location.IGpsNavigationMessageListener;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
/**
* An base implementation for GPS navigation messages provider.
@@ -29,8 +31,10 @@ import android.os.RemoteException;
*/
public abstract class GpsNavigationMessageProvider
extends RemoteListenerHelper<IGpsNavigationMessageListener> {
public GpsNavigationMessageProvider() {
super("GpsNavigationMessageProvider");
private static final String TAG = "GpsNavigationMessageProvider";
public GpsNavigationMessageProvider(Handler handler) {
super(handler, TAG);
}
public void onNavigationMessageAvailable(final GpsNavigationMessageEvent event) {
@@ -42,7 +46,57 @@ public abstract class GpsNavigationMessageProvider
listener.onGpsNavigationMessageReceived(event);
}
};
foreach(operation);
}
public void onCapabilitiesUpdated(boolean isGpsNavigationMessageSupported) {
int status = isGpsNavigationMessageSupported ?
GpsNavigationMessageEvent.STATUS_READY :
GpsNavigationMessageEvent.STATUS_NOT_SUPPORTED;
setSupported(isGpsNavigationMessageSupported, new StatusChangedOperation(status));
}
@Override
protected ListenerOperation<IGpsNavigationMessageListener> getHandlerOperation(int result) {
final int status;
switch (result) {
case RESULT_SUCCESS:
status = GpsNavigationMessageEvent.STATUS_READY;
break;
case RESULT_NOT_AVAILABLE:
case RESULT_NOT_SUPPORTED:
case RESULT_INTERNAL_ERROR:
status = GpsNavigationMessageEvent.STATUS_NOT_SUPPORTED;
break;
case RESULT_GPS_LOCATION_DISABLED:
status = GpsNavigationMessageEvent.STATUS_GPS_LOCATION_DISABLED;
break;
default:
Log.v(TAG, "Unhandled addListener result: " + result);
return null;
}
return new StatusChangedOperation(status);
}
@Override
protected void handleGpsEnabledChanged(boolean enabled) {
int status = enabled ?
GpsNavigationMessageEvent.STATUS_READY :
GpsNavigationMessageEvent.STATUS_GPS_LOCATION_DISABLED;
foreach(new StatusChangedOperation(status));
}
private class StatusChangedOperation
implements ListenerOperation<IGpsNavigationMessageListener> {
private final int mStatus;
public StatusChangedOperation(int status) {
mStatus = status;
}
@Override
public void execute(IGpsNavigationMessageListener listener) throws RemoteException {
listener.onStatusChanged(mStatus);
}
}
}

View File

@@ -17,14 +17,55 @@
package com.android.server.location;
import android.location.IGpsStatusListener;
import android.os.Handler;
import android.os.RemoteException;
/**
* Implementation of a handler for {@link IGpsStatusListener}.
*/
abstract class GpsStatusListenerHelper extends RemoteListenerHelper<IGpsStatusListener> {
public GpsStatusListenerHelper() {
super("GpsStatusListenerHelper");
public GpsStatusListenerHelper(Handler handler) {
super(handler, "GpsStatusListenerHelper");
Operation nullOperation = new Operation() {
@Override
public void execute(IGpsStatusListener iGpsStatusListener) throws RemoteException {}
};
setSupported(GpsLocationProvider.isSupported(), nullOperation);
}
@Override
protected boolean registerWithService() {
return true;
}
@Override
protected void unregisterFromService() {}
@Override
protected ListenerOperation<IGpsStatusListener> getHandlerOperation(int result) {
return null;
}
@Override
protected void handleGpsEnabledChanged(boolean enabled) {
Operation operation;
if (enabled) {
operation = new Operation() {
@Override
public void execute(IGpsStatusListener listener) throws RemoteException {
listener.onGpsStarted();
}
};
} else {
operation = new Operation() {
@Override
public void execute(IGpsStatusListener listener) throws RemoteException {
listener.onGpsStopped();
}
};
}
foreach(operation);
}
public void onFirstFix(final int timeToFirstFix) {
@@ -34,22 +75,6 @@ abstract class GpsStatusListenerHelper extends RemoteListenerHelper<IGpsStatusLi
listener.onFirstFix(timeToFirstFix);
}
};
foreach(operation);
}
public void onStatusChanged(final boolean isNavigating) {
Operation operation = new Operation() {
@Override
public void execute(IGpsStatusListener listener) throws RemoteException {
if (isNavigating) {
listener.onGpsStarted();
} else {
listener.onGpsStopped();
}
}
};
foreach(operation);
}
@@ -76,7 +101,6 @@ abstract class GpsStatusListenerHelper extends RemoteListenerHelper<IGpsStatusLi
usedInFixMask);
}
};
foreach(operation);
}
@@ -87,7 +111,6 @@ abstract class GpsStatusListenerHelper extends RemoteListenerHelper<IGpsStatusLi
listener.onNmeaReceived(timestamp, nmea);
}
};
foreach(operation);
}

View File

@@ -19,35 +19,41 @@ package com.android.server.location;
import com.android.internal.util.Preconditions;
import android.annotation.NonNull;
import android.os.Handler;
import android.os.IBinder;
import android.os.IInterface;
import android.os.RemoteException;
import android.util.Log;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
/**
* A helper class, that handles operations in remote listeners, and tracks for remote process death.
*/
abstract class RemoteListenerHelper<TListener extends IInterface> {
private final String mTag;
private final HashMap<IBinder, LinkedListener> mListenerMap =
new HashMap<IBinder, LinkedListener>();
protected static final int RESULT_SUCCESS = 0;
protected static final int RESULT_NOT_AVAILABLE = 1;
protected static final int RESULT_NOT_SUPPORTED = 2;
protected static final int RESULT_GPS_LOCATION_DISABLED = 3;
protected static final int RESULT_INTERNAL_ERROR = 4;
protected RemoteListenerHelper(String name) {
private final Handler mHandler;
private final String mTag;
private final HashMap<IBinder, LinkedListener> mListenerMap = new HashMap<>();
private boolean mIsRegistered;
private boolean mHasIsSupported;
private boolean mIsSupported;
protected RemoteListenerHelper(Handler handler, String name) {
Preconditions.checkNotNull(name);
mHandler = handler;
mTag = name;
}
public boolean addListener(@NonNull TListener listener) {
Preconditions.checkNotNull(listener, "Attempted to register a 'null' listener.");
if (!isSupported()) {
Log.e(mTag, "Refused to add listener, the feature is not supported.");
return false;
}
IBinder binder = listener.asBinder();
LinkedListener deathListener = new LinkedListener(listener);
synchronized (mListenerMap) {
@@ -55,77 +61,128 @@ abstract class RemoteListenerHelper<TListener extends IInterface> {
// listener already added
return true;
}
try {
binder.linkToDeath(deathListener, 0 /* flags */);
} catch (RemoteException e) {
// if the remote process registering the listener is already death, just swallow the
// exception and continue
Log.e(mTag, "Remote listener already died.", e);
// exception and return
Log.v(mTag, "Remote listener already died.", e);
return false;
}
mListenerMap.put(binder, deathListener);
if (mListenerMap.size() == 1) {
if (!registerWithService()) {
Log.e(mTag, "RegisterWithService failed, listener will be removed.");
removeListener(listener);
return false;
}
}
}
// update statuses we already know about, starting from the ones that will never change
int result;
if (!isAvailableInPlatform()) {
result = RESULT_NOT_AVAILABLE;
} else if (mHasIsSupported && !mIsSupported) {
result = RESULT_NOT_SUPPORTED;
} else if (!isGpsEnabled()) {
result = RESULT_GPS_LOCATION_DISABLED;
} else if (!tryRegister()) {
// only attempt to register if GPS is enabled, otherwise we will register once GPS
// becomes available
result = RESULT_INTERNAL_ERROR;
} else if (mHasIsSupported && mIsSupported) {
result = RESULT_SUCCESS;
} else {
// at this point if the supported flag is not set, the notification will be sent
// asynchronously in the future
return true;
}
post(listener, getHandlerOperation(result));
}
return true;
}
public boolean removeListener(@NonNull TListener listener) {
public void removeListener(@NonNull TListener listener) {
Preconditions.checkNotNull(listener, "Attempted to remove a 'null' listener.");
if (!isSupported()) {
Log.e(mTag, "Refused to remove listener, the feature is not supported.");
return false;
}
IBinder binder = listener.asBinder();
LinkedListener linkedListener;
synchronized (mListenerMap) {
linkedListener = mListenerMap.remove(binder);
if (mListenerMap.isEmpty() && linkedListener != null) {
unregisterFromService();
if (mListenerMap.isEmpty()) {
tryUnregister();
}
}
if (linkedListener != null) {
binder.unlinkToDeath(linkedListener, 0 /* flags */);
}
return true;
}
protected abstract boolean isSupported();
public void onGpsEnabledChanged(boolean enabled) {
// handle first the sub-class implementation, so any error in registration can take
// precedence
handleGpsEnabledChanged(enabled);
synchronized (mListenerMap) {
if (!enabled) {
tryUnregister();
return;
}
if (mListenerMap.isEmpty()) {
return;
}
if (tryRegister()) {
// registration was successful, there is no need to update the state
return;
}
ListenerOperation<TListener> operation = getHandlerOperation(RESULT_INTERNAL_ERROR);
foreachUnsafe(operation);
}
}
protected abstract boolean isAvailableInPlatform();
protected abstract boolean isGpsEnabled();
protected abstract boolean registerWithService();
protected abstract void unregisterFromService();
protected abstract ListenerOperation<TListener> getHandlerOperation(int result);
protected abstract void handleGpsEnabledChanged(boolean enabled);
protected interface ListenerOperation<TListener extends IInterface> {
void execute(TListener listener) throws RemoteException;
}
protected void foreach(ListenerOperation operation) {
Collection<LinkedListener> linkedListeners;
protected void foreach(ListenerOperation<TListener> operation) {
synchronized (mListenerMap) {
Collection<LinkedListener> values = mListenerMap.values();
linkedListeners = new ArrayList<LinkedListener>(values);
foreachUnsafe(operation);
}
}
for (LinkedListener linkedListener : linkedListeners) {
TListener listener = linkedListener.getUnderlyingListener();
try {
operation.execute(listener);
} catch (RemoteException e) {
Log.e(mTag, "Error in monitored listener.", e);
removeListener(listener);
}
protected void setSupported(boolean value, ListenerOperation<TListener> notifier) {
synchronized (mListenerMap) {
mHasIsSupported = true;
mIsSupported = value;
foreachUnsafe(notifier);
}
}
private void foreachUnsafe(ListenerOperation<TListener> operation) {
for (LinkedListener linkedListener : mListenerMap.values()) {
post(linkedListener.getUnderlyingListener(), operation);
}
}
private void post(TListener listener, ListenerOperation<TListener> operation) {
if (operation != null) {
mHandler.post(new HandlerRunnable(listener, operation));
}
}
private boolean tryRegister() {
if (!mIsRegistered) {
mIsRegistered = registerWithService();
}
return mIsRegistered;
}
private void tryUnregister() {
if (!mIsRegistered) {
return;
}
unregisterFromService();
mIsRegistered = false;
}
private class LinkedListener implements IBinder.DeathRecipient {
private final TListener mListener;
@@ -144,4 +201,23 @@ abstract class RemoteListenerHelper<TListener extends IInterface> {
removeListener(mListener);
}
}
private class HandlerRunnable implements Runnable {
private final TListener mListener;
private final ListenerOperation<TListener> mOperation;
public HandlerRunnable(TListener listener, ListenerOperation<TListener> operation) {
mListener = listener;
mOperation = operation;
}
@Override
public void run() {
try {
mOperation.execute(mListener);
} catch (RemoteException e) {
Log.v(mTag, "Error in monitored listener.", e);
}
}
}
}