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