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
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 @@
  */
 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 @@
     protected boolean registerWithServer() throws RemoteException {
         return mLocationManager.addGpsMeasurementsListener(
                 mListenerTransport,
-                mContext.getPackageName());
+                getContext().getPackageName());
     }
 
     @Override
@@ -59,7 +57,18 @@
                     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);
         }
     }
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 @@
  * @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 @@
      * @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 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.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 @@
  * @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 @@
      * @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 @@
  */
 class GpsNavigationMessageListenerTransport
         extends LocalListenerHelper<GpsNavigationMessageEvent.Listener> {
-    private final Context mContext;
     private final ILocationManager mLocationManager;
 
     private final IGpsNavigationMessageListener mListenerTransport = new ListenerTransport();
@@ -34,8 +33,7 @@
     public GpsNavigationMessageListenerTransport(
             Context context,
             ILocationManager locationManager) {
-        super("GpsNavigationMessageListenerTransport");
-        mContext = context;
+        super(context, "GpsNavigationMessageListenerTransport");
         mLocationManager = locationManager;
     }
 
@@ -43,7 +41,7 @@
     protected boolean registerWithServer() throws RemoteException {
         return mLocationManager.addGpsNavigationMessageListener(
                 mListenerTransport,
-                mContext.getPackageName());
+                getContext().getPackageName());
     }
 
     @Override
@@ -62,7 +60,19 @@
                     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);
         }
     }
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 @@
  */
 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 @@
  */
 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 @@
     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();
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 @@
 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 @@
  * @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 @@
                     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 @@
                 try {
                     unregisterFromServer();
                 } catch (RemoteException e) {
-
+                    Log.v(mTag, "Error handling last listener removal", e);
                 }
             }
         }
@@ -91,12 +91,15 @@
         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);
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 @@
      * 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 @@
      * 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.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 @@
     }
 
     @Override
-    public boolean removeGpsMeasurementsListener(IGpsMeasurementsListener listener) {
-        return mGpsMeasurementsProvider.removeListener(listener);
+    public void removeGpsMeasurementsListener(IGpsMeasurementsListener listener) {
+        mGpsMeasurementsProvider.removeListener(listener);
     }
 
     @Override
@@ -1888,8 +1890,8 @@
     }
 
     @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 @@
     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 @@
     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 @@
         }
     };
 
-    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 @@
                         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 @@
         }
 
         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 @@
             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 @@
 
 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 @@
  */
 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 @@
                 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);
+        }
+    }
 }
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 @@
 
 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 @@
  */
 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 @@
                         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);
+        }
+    }
 }
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,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 @@
                 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 @@
                         usedInFixMask);
             }
         };
-
         foreach(operation);
     }
 
@@ -87,7 +111,6 @@
                 listener.onNmeaReceived(timestamp, nmea);
             }
         };
-
         foreach(operation);
     }
 
diff --git a/services/core/java/com/android/server/location/RemoteListenerHelper.java b/services/core/java/com/android/server/location/RemoteListenerHelper.java
index 451af18..402b601 100644
--- a/services/core/java/com/android/server/location/RemoteListenerHelper.java
+++ b/services/core/java/com/android/server/location/RemoteListenerHelper.java
@@ -19,35 +19,41 @@
 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 @@
                 // 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 @@
             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);
+            }
+        }
+    }
 }