From 6568d709e78d6ccaf256b7d0e4a19cdfb26deafb Mon Sep 17 00:00:00 2001 From: destradaa Date: Mon, 27 Oct 2014 12:47:41 -0700 Subject: Add support for GPS measurement/navigation message capabilities. b/16727892 b/16815124 The listeners are changed to receive statuses asynchronously, this is required because GPS HAL, requires time to be notified of the capabilities it supports. Change-Id: Ie69fdd629d8680341386a2c736bc851632dd2bda --- .../location/GpsMeasurementListenerTransport.java | 17 ++- .../android/location/GpsMeasurementsEvent.java | 31 +++- .../location/GpsNavigationMessageEvent.java | 30 +++- .../GpsNavigationMessageListenerTransport.java | 18 ++- .../android/location/IGpsMeasurementsListener.aidl | 1 + .../location/IGpsNavigationMessageListener.aidl | 1 + .../java/android/location/ILocationManager.aidl | 4 +- .../java/android/location/LocalListenerHelper.java | 25 ++-- .../java/android/location/LocationManager.java | 4 +- .../com/android/server/LocationManagerService.java | 10 +- .../server/location/GpsLocationProvider.java | 120 ++++++++------- .../server/location/GpsMeasurementsProvider.java | 59 +++++++- .../location/GpsNavigationMessageProvider.java | 60 +++++++- .../server/location/GpsStatusListenerHelper.java | 57 ++++--- .../server/location/RemoteListenerHelper.java | 164 +++++++++++++++------ 15 files changed, 453 insertions(+), 148 deletions(-) diff --git a/location/java/android/location/GpsMeasurementListenerTransport.java b/location/java/android/location/GpsMeasurementListenerTransport.java index 2d9a372..610da96 100644 --- a/location/java/android/location/GpsMeasurementListenerTransport.java +++ b/location/java/android/location/GpsMeasurementListenerTransport.java @@ -26,14 +26,12 @@ import android.os.RemoteException; */ class GpsMeasurementListenerTransport extends LocalListenerHelper { - 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 operation = + new ListenerOperation() { + @Override + public void execute(GpsMeasurementsEvent.Listener listener) throws RemoteException { + listener.onStatusChanged(status); + } + }; foreach(operation); } } diff --git a/location/java/android/location/GpsMeasurementsEvent.java b/location/java/android/location/GpsMeasurementsEvent.java index e04ed81..94ca920 100644 --- a/location/java/android/location/GpsMeasurementsEvent.java +++ b/location/java/android/location/GpsMeasurementsEvent.java @@ -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 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); } diff --git a/location/java/android/location/GpsNavigationMessageEvent.java b/location/java/android/location/GpsNavigationMessageEvent.java index 50ffa75..b61dac0 100644 --- a/location/java/android/location/GpsNavigationMessageEvent.java +++ b/location/java/android/location/GpsNavigationMessageEvent.java @@ -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) { diff --git a/location/java/android/location/GpsNavigationMessageListenerTransport.java b/location/java/android/location/GpsNavigationMessageListenerTransport.java index ec4812b..f6ba407 100644 --- a/location/java/android/location/GpsNavigationMessageListenerTransport.java +++ b/location/java/android/location/GpsNavigationMessageListenerTransport.java @@ -26,7 +26,6 @@ import android.os.RemoteException; */ class GpsNavigationMessageListenerTransport extends LocalListenerHelper { - 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 operation = + new ListenerOperation() { + @Override + public void execute(GpsNavigationMessageEvent.Listener listener) + throws RemoteException { + listener.onStatusChanged(status); + } + }; foreach(operation); } } diff --git a/location/java/android/location/IGpsMeasurementsListener.aidl b/location/java/android/location/IGpsMeasurementsListener.aidl index b34bb6c..cbd3100 100644 --- a/location/java/android/location/IGpsMeasurementsListener.aidl +++ b/location/java/android/location/IGpsMeasurementsListener.aidl @@ -23,4 +23,5 @@ import android.location.GpsMeasurementsEvent; */ oneway interface IGpsMeasurementsListener { void onGpsMeasurementsReceived(in GpsMeasurementsEvent event); + void onStatusChanged(in int status); } diff --git a/location/java/android/location/IGpsNavigationMessageListener.aidl b/location/java/android/location/IGpsNavigationMessageListener.aidl index 18603fe..a708ea6 100644 --- a/location/java/android/location/IGpsNavigationMessageListener.aidl +++ b/location/java/android/location/IGpsNavigationMessageListener.aidl @@ -23,4 +23,5 @@ import android.location.GpsNavigationMessageEvent; */ oneway interface IGpsNavigationMessageListener { void onGpsNavigationMessageReceived(in GpsNavigationMessageEvent event); + void onStatusChanged(in int status); } diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index 1501710..af76175 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -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 getAllProviders(); diff --git a/location/java/android/location/LocalListenerHelper.java b/location/java/android/location/LocalListenerHelper.java index 1f3bf67..458c451 100644 --- a/location/java/android/location/LocalListenerHelper.java +++ b/location/java/android/location/LocalListenerHelper.java @@ -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 { - private final HashSet mListeners = new HashSet(); + private final HashSet mListeners = new HashSet<>(); + private final String mTag; + private final Context mContext; - protected LocalListenerHelper(String name) { + 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 { 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 { try { unregisterFromServer(); } catch (RemoteException e) { - + Log.v(mTag, "Error handling last listener removal", e); } } } @@ -91,12 +91,15 @@ abstract class LocalListenerHelper { void execute(TListener listener) throws RemoteException; } - protected void foreach(ListenerOperation operation) { + protected Context getContext() { + return mContext; + } + + protected void foreach(ListenerOperation operation) { Collection listeners; synchronized (mListeners) { - listeners = new ArrayList(mListeners); + listeners = new ArrayList<>(mListeners); } - for (TListener listener : listeners) { try { operation.execute(listener); diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index ed408e0..513a627 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -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 */ diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index d9c96e4..be83b9b 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -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 diff --git a/services/core/java/com/android/server/location/GpsLocationProvider.java b/services/core/java/com/android/server/location/GpsLocationProvider.java index 0198e46..189de83 100644 --- a/services/core/java/com/android/server/location/GpsLocationProvider.java +++ b/services/core/java/com/android/server/location/GpsLocationProvider.java @@ -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; } @@ -694,6 +651,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() { @@ -1443,7 +1456,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); @@ -1596,6 +1611,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); } /** diff --git a/services/core/java/com/android/server/location/GpsMeasurementsProvider.java b/services/core/java/com/android/server/location/GpsMeasurementsProvider.java index 1c48257..0514e0c 100644 --- a/services/core/java/com/android/server/location/GpsMeasurementsProvider.java +++ b/services/core/java/com/android/server/location/GpsMeasurementsProvider.java @@ -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 { - 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 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 { + private final int mStatus; + + public StatusChangedOperation(int status) { + mStatus = status; + } + + @Override + public void execute(IGpsMeasurementsListener listener) throws RemoteException { + listener.onStatusChanged(mStatus); + } + } } diff --git a/services/core/java/com/android/server/location/GpsNavigationMessageProvider.java b/services/core/java/com/android/server/location/GpsNavigationMessageProvider.java index fca7378..13d22fc 100644 --- a/services/core/java/com/android/server/location/GpsNavigationMessageProvider.java +++ b/services/core/java/com/android/server/location/GpsNavigationMessageProvider.java @@ -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 { - 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 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 { + private final int mStatus; + + public StatusChangedOperation(int status) { + mStatus = status; + } + + @Override + public void execute(IGpsNavigationMessageListener listener) throws RemoteException { + listener.onStatusChanged(mStatus); + } + } } diff --git a/services/core/java/com/android/server/location/GpsStatusListenerHelper.java b/services/core/java/com/android/server/location/GpsStatusListenerHelper.java index 27cf3d8..376b4a5 100644 --- a/services/core/java/com/android/server/location/GpsStatusListenerHelper.java +++ b/services/core/java/com/android/server/location/GpsStatusListenerHelper.java @@ -17,39 +17,64 @@ 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 { - public GpsStatusListenerHelper() { - super("GpsStatusListenerHelper"); - } + public GpsStatusListenerHelper(Handler handler) { + super(handler, "GpsStatusListenerHelper"); - public void onFirstFix(final int timeToFirstFix) { - Operation operation = new Operation() { + Operation nullOperation = new Operation() { @Override - public void execute(IGpsStatusListener listener) throws RemoteException { - listener.onFirstFix(timeToFirstFix); - } + public void execute(IGpsStatusListener iGpsStatusListener) throws RemoteException {} }; + setSupported(GpsLocationProvider.isSupported(), nullOperation); + } + + @Override + protected boolean registerWithService() { + return true; + } + + @Override + protected void unregisterFromService() {} + + @Override + protected ListenerOperation 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 onStatusChanged(final boolean isNavigating) { + public void onFirstFix(final int timeToFirstFix) { Operation operation = new Operation() { @Override public void execute(IGpsStatusListener listener) throws RemoteException { - if (isNavigating) { - listener.onGpsStarted(); - } else { - listener.onGpsStopped(); - } + listener.onFirstFix(timeToFirstFix); } }; - foreach(operation); } @@ -76,7 +101,6 @@ abstract class GpsStatusListenerHelper extends RemoteListenerHelper { + 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; + + private final Handler mHandler; private final String mTag; - private final HashMap mListenerMap = - new HashMap(); - protected RemoteListenerHelper(String name) { + private final HashMap 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,75 +61,126 @@ abstract class RemoteListenerHelper { // 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 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 getHandlerOperation(int result); + protected abstract void handleGpsEnabledChanged(boolean enabled); protected interface ListenerOperation { void execute(TListener listener) throws RemoteException; } - protected void foreach(ListenerOperation operation) { - Collection linkedListeners; + protected void foreach(ListenerOperation operation) { synchronized (mListenerMap) { - Collection values = mListenerMap.values(); - linkedListeners = new ArrayList(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 notifier) { + synchronized (mListenerMap) { + mHasIsSupported = true; + mIsSupported = value; + foreachUnsafe(notifier); + } + } + + private void foreachUnsafe(ListenerOperation operation) { + for (LinkedListener linkedListener : mListenerMap.values()) { + post(linkedListener.getUnderlyingListener(), operation); + } + } + + private void post(TListener listener, ListenerOperation 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 { @@ -144,4 +201,23 @@ abstract class RemoteListenerHelper { removeListener(mListener); } } + + private class HandlerRunnable implements Runnable { + private final TListener mListener; + private final ListenerOperation mOperation; + + public HandlerRunnable(TListener listener, ListenerOperation operation) { + mListener = listener; + mOperation = operation; + } + + @Override + public void run() { + try { + mOperation.execute(mListener); + } catch (RemoteException e) { + Log.v(mTag, "Error in monitored listener.", e); + } + } + } } -- cgit v1.1