Made it easier to disable overlay mechanism of location components.

Fixed b/8276827

Vendor might want to provide their own implementation of "network
location", "fused location" and "geocoder" service. Location manager now
allows those service to be replaced by packages that have the same
signature as one of the packages in config_locationProviderPackageNames.
Such behavior might not be desirable on some devices. This change
make this behavior configurable by 3 boolean flags.

Details:
- Added three boolean flags in core/res/res/values/config.xml to enable
or disable NLP/FLP/Geocoder overlay
- Added 3 package name for the stock NLP/FLP/Geocoder. They are needed
  only when overlay is disabled because LocationManagerService need to
  know which package is preferred when searching for
  NLP/FLP/Geocoder service.
- Made ServiceWatcher able to handle non-overlayable services.
- Fixed an NPE isue in ServiceWatcher. mPm.queryIntentServicesAsUser
  might return null.
- Fixed an bug: justCheckThisPackage in bindBestPackageLocked is always
  ignored.

Change-Id: Id221961ac7c3aa8ad44b894f9523f04f770ae237
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 84e300a..5c772b2 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -633,6 +633,60 @@
     <!-- True if WallpaperService is enabled -->
     <bool name="config_enableWallpaperService">true</bool>
 
+    <!-- Whether to enable network location overlay which allows network
+         location provider to be replaced by an app at run-time. When disabled,
+         only the config_networkLocationProviderPackageName package will be
+         searched for network location provider, otherwise packages whose
+         signature matches the signatures of config_locationProviderPackageNames
+         will be searched, and the service with the highest version number will
+         be picked. Anyone who wants to disable the overlay mechanism can set it
+         to false.
+         -->
+    <bool name="config_enableNetworkLocationOverlay" translatable="false">true</bool>
+    <!-- Package name providing network location support. Used only when
+         config_enableNetworkLocationOverlay is false. -->
+    <string name="config_networkLocationProviderPackageName" translatable="false">@null</string>
+
+    <!-- Whether to enable fused location provider overlay which allows fused
+         location provider to be replaced by an app at run-time. When disabled,
+         only the config_fusedLocationProviderPackageName package will be
+         searched for fused location provider, otherwise packages whose
+         signature matches the signatures of config_locationProviderPackageNames
+         will be searched, and the service with the highest version number will
+         be picked. Anyone who wants to disable the overlay mechanism can set it
+         to false.
+         -->
+    <bool name="config_enableFusedLocationOverlay" translatable="false">true</bool>
+    <!-- Package name providing fused location support. Used only when
+         config_enableFusedLocationOverlay is false. -->
+    <string name="config_fusedLocationProviderPackageName" translatable="false">com.android.location.fused</string>
+
+    <!-- Whether to enable geocoder overlay which allows geocoder to be replaced
+         by an app at run-time. When disabled, only the
+         config_geocoderProviderPackageName package will be searched for
+         geocoder, otherwise packages whose signature matches the signatures of
+         config_locationProviderPackageNames will be searched, and the service
+         with the highest version number will be picked. Anyone who wants to
+         disable the overlay mechanism can set it to false.
+         -->
+    <bool name="config_enableGeocoderOverlay" translatable="false">true</bool>
+    <!-- Package name providing geocoder API support. Used only when
+         config_enableGeocoderOverlay is false. -->
+    <string name="config_geocoderProviderPackageName" translatable="false">@null</string>
+
+    <!-- Whether to enable geofence overlay which allows geofence to be replaced
+         by an app at run-time. When disabled, only the
+         config_geofenceProviderPackageName package will be searched for
+         geofence implementation, otherwise packages whose signature matches the
+         signatures of config_locationProviderPackageNames will be searched, and
+         the service with the highest version number will be picked. Anyone who
+         wants to disable the overlay mechanism can set it to false.
+         -->
+    <bool name="config_enableGeofenceOverlay" translatable="false">true</bool>
+    <!-- Package name providing geofence API support. Used only when
+         config_enableGeofenceOverlay is false. -->
+    <string name="config_geofenceProviderPackageName" translatable="false">@null</string>
+
     <!-- Package name(s) containing location provider support.
          These packages can contain services implementing location providers,
          such as the Geocode Provider, Network Location Provider, and
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index fdf7c8f..2158e90 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1534,6 +1534,10 @@
   <java-symbol type="array" name="config_notificationFallbackVibePattern" />
   <java-symbol type="bool" name="config_animateScreenLights" />
   <java-symbol type="bool" name="config_automatic_brightness_available" />
+  <java-symbol type="bool" name="config_enableFusedLocationOverlay" />
+  <java-symbol type="bool" name="config_enableGeocoderOverlay" />
+  <java-symbol type="bool" name="config_enableGeofenceOverlay" />
+  <java-symbol type="bool" name="config_enableNetworkLocationOverlay" />
   <java-symbol type="bool" name="config_sf_limitedAlpha" />
   <java-symbol type="bool" name="config_unplugTurnsOnScreen" />
   <java-symbol type="bool" name="config_wifi_background_scan_support" />
@@ -1618,6 +1622,10 @@
   <java-symbol type="string" name="car_mode_disable_notification_title" />
   <java-symbol type="string" name="chooser_wallpaper" />
   <java-symbol type="string" name="config_datause_iface" />
+  <java-symbol type="string" name="config_fusedLocationProviderPackageName" />
+  <java-symbol type="string" name="config_geocoderProviderPackageName" />
+  <java-symbol type="string" name="config_geofenceProviderPackageName" />
+  <java-symbol type="string" name="config_networkLocationProviderPackageName" />
   <java-symbol type="string" name="config_wimaxManagerClassname" />
   <java-symbol type="string" name="config_wimaxNativeLibLocation" />
   <java-symbol type="string" name="config_wimaxServiceClassname" />
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index b47e8a0..2675309 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -380,7 +380,10 @@
                 mContext,
                 LocationManager.NETWORK_PROVIDER,
                 NETWORK_LOCATION_SERVICE_ACTION,
-                providerPackageNames, mLocationHandler);
+                com.android.internal.R.bool.config_enableNetworkLocationOverlay,
+                com.android.internal.R.string.config_networkLocationProviderPackageName,
+                com.android.internal.R.array.config_locationProviderPackageNames,
+                mLocationHandler);
         if (networkProvider != null) {
             mRealProviders.put(LocationManager.NETWORK_PROVIDER, networkProvider);
             mProxyProviders.add(networkProvider);
@@ -394,7 +397,10 @@
                 mContext,
                 LocationManager.FUSED_PROVIDER,
                 FUSED_LOCATION_SERVICE_ACTION,
-                providerPackageNames, mLocationHandler);
+                com.android.internal.R.bool.config_enableFusedLocationOverlay,
+                com.android.internal.R.string.config_fusedLocationProviderPackageName,
+                com.android.internal.R.array.config_locationProviderPackageNames,
+                mLocationHandler);
         if (fusedLocationProvider != null) {
             addProviderLocked(fusedLocationProvider);
             mProxyProviders.add(fusedLocationProvider);
@@ -406,15 +412,22 @@
         }
 
         // bind to geocoder provider
-        mGeocodeProvider = GeocoderProxy.createAndBind(mContext, providerPackageNames,
+        mGeocodeProvider = GeocoderProxy.createAndBind(mContext,
+                com.android.internal.R.bool.config_enableGeocoderOverlay,
+                com.android.internal.R.string.config_geocoderProviderPackageName,
+                com.android.internal.R.array.config_locationProviderPackageNames,
                 mLocationHandler);
         if (mGeocodeProvider == null) {
             Slog.e(TAG,  "no geocoder provider found");
         }
 
         // bind to geofence provider
-        GeofenceProxy provider = GeofenceProxy.createAndBind(mContext, providerPackageNames,
-                mLocationHandler, gpsProvider.getGpsGeofenceProxy());
+        GeofenceProxy provider = GeofenceProxy.createAndBind(mContext,
+                com.android.internal.R.bool.config_enableGeofenceOverlay,
+                com.android.internal.R.string.config_geofenceProviderPackageName,
+                com.android.internal.R.array.config_locationProviderPackageNames,
+                mLocationHandler,
+                gpsProvider.getGpsGeofenceProxy());
         if (provider == null) {
             Slog.e(TAG,  "no geofence provider found");
         }
@@ -1290,14 +1303,14 @@
         if (name == null) {
             throw new IllegalArgumentException("provider name must not be null");
         }
+
+        if (D) Log.d(TAG, "request " + Integer.toHexString(System.identityHashCode(receiver))
+                + " " + name + " " + request + " from " + packageName + "(" + uid + ")");
         LocationProviderInterface provider = mProvidersByName.get(name);
         if (provider == null) {
             throw new IllegalArgumentException("provider doesn't exisit: " + provider);
         }
 
-        if (D) Log.d(TAG, "request " + Integer.toHexString(System.identityHashCode(receiver))
-                + " " + name + " " + request + " from " + packageName + "(" + uid + ")");
-
         UpdateRecord record = new UpdateRecord(name, request, receiver);
         UpdateRecord oldRecord = receiver.mUpdateRecords.put(name, record);
         if (oldRecord != null) {
diff --git a/services/java/com/android/server/ServiceWatcher.java b/services/java/com/android/server/ServiceWatcher.java
index 6078d8a..5c7bfab 100644
--- a/services/java/com/android/server/ServiceWatcher.java
+++ b/services/java/com/android/server/ServiceWatcher.java
@@ -27,6 +27,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.Signature;
+import android.content.res.Resources;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.UserHandle;
@@ -36,6 +37,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 
@@ -53,6 +55,13 @@
     private final PackageManager mPm;
     private final List<HashSet<Signature>> mSignatureSets;
     private final String mAction;
+
+    /**
+     * If mServicePackageName is not null, only this package will be searched for the service that
+     * implements mAction. When null, all packages in the system that matches one of the signature
+     * in mSignatureSets are searched.
+     */
+    private final String mServicePackageName;
     private final Runnable mNewServiceWork;
     private final Handler mHandler;
 
@@ -87,19 +96,40 @@
     }
 
     public ServiceWatcher(Context context, String logTag, String action,
-            List<String> initialPackageNames, Runnable newServiceWork, Handler handler) {
+            int overlaySwitchResId, int defaultServicePackageNameResId,
+            int initialPackageNamesResId, Runnable newServiceWork,
+            Handler handler) {
         mContext = context;
         mTag = logTag;
         mAction = action;
         mPm = mContext.getPackageManager();
         mNewServiceWork = newServiceWork;
         mHandler = handler;
+        Resources resources = context.getResources();
+
+        // Whether to enable service overlay.
+        boolean enableOverlay = resources.getBoolean(overlaySwitchResId);
+        ArrayList<String>  initialPackageNames = new ArrayList<String>();
+        if (enableOverlay) {
+            // A list of package names used to create the signatures.
+            String[] pkgs = resources.getStringArray(initialPackageNamesResId);
+            if (pkgs != null) initialPackageNames.addAll(Arrays.asList(pkgs));
+            mServicePackageName = null;
+            if (D) Log.d(mTag, "Overlay enabled, packages=" + Arrays.toString(pkgs));
+        } else {
+            // The default package name that is searched for service implementation when overlay is
+            // disabled.
+            String servicePackageName = resources.getString(defaultServicePackageNameResId);
+            if (servicePackageName != null) initialPackageNames.add(servicePackageName);
+            mServicePackageName = servicePackageName;
+            if (D) Log.d(mTag, "Overlay disabled, default package=" + servicePackageName);
+        }
         mSignatureSets = getSignatureSets(context, initialPackageNames);
     }
 
     public boolean start() {
         synchronized (mLock) {
-            if (!bindBestPackageLocked(null)) return false;
+            if (!bindBestPackageLocked(mServicePackageName)) return false;
         }
 
         // listen for user change
@@ -115,8 +145,10 @@
             }
         }, UserHandle.ALL, intentFilter, null, mHandler);
 
-        // listen for relevant package changes
-        mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
+        // listen for relevant package changes if service overlay is enabled.
+        if (mServicePackageName == null) {
+            mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
+        }
 
         return true;
     }
@@ -133,50 +165,55 @@
         if (justCheckThisPackage != null) {
             intent.setPackage(justCheckThisPackage);
         }
-        List<ResolveInfo> rInfos = mPm.queryIntentServicesAsUser(new Intent(mAction),
+        List<ResolveInfo> rInfos = mPm.queryIntentServicesAsUser(intent,
                 PackageManager.GET_META_DATA, UserHandle.USER_OWNER);
         int bestVersion = Integer.MIN_VALUE;
         String bestPackage = null;
         boolean bestIsMultiuser = false;
-        for (ResolveInfo rInfo : rInfos) {
-            String packageName = rInfo.serviceInfo.packageName;
+        if (rInfos != null) {
+            for (ResolveInfo rInfo : rInfos) {
+                String packageName = rInfo.serviceInfo.packageName;
 
-            // check signature
-            try {
-                PackageInfo pInfo;
-                pInfo = mPm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
-                if (!isSignatureMatch(pInfo.signatures)) {
-                    Log.w(mTag, packageName + " resolves service " + mAction +
-                            ", but has wrong signature, ignoring");
+                // check signature
+                try {
+                    PackageInfo pInfo;
+                    pInfo = mPm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+                    if (!isSignatureMatch(pInfo.signatures)) {
+                        Log.w(mTag, packageName + " resolves service " + mAction
+                                + ", but has wrong signature, ignoring");
+                        continue;
+                    }
+                } catch (NameNotFoundException e) {
+                    Log.wtf(mTag, e);
                     continue;
                 }
-            } catch (NameNotFoundException e) {
-                Log.wtf(mTag, e);
-                continue;
+
+                // check metadata
+                int version = Integer.MIN_VALUE;
+                boolean isMultiuser = false;
+                if (rInfo.serviceInfo.metaData != null) {
+                    version = rInfo.serviceInfo.metaData.getInt(
+                            EXTRA_SERVICE_VERSION, Integer.MIN_VALUE);
+                    isMultiuser = rInfo.serviceInfo.metaData.getBoolean(EXTRA_SERVICE_IS_MULTIUSER);
+                }
+
+                if (version > mVersion) {
+                    bestVersion = version;
+                    bestPackage = packageName;
+                    bestIsMultiuser = isMultiuser;
+                }
             }
 
-            // check metadata
-            int version = Integer.MIN_VALUE;
-            boolean isMultiuser = false;
-            if (rInfo.serviceInfo.metaData != null) {
-                version = rInfo.serviceInfo.metaData.getInt(EXTRA_SERVICE_VERSION,
-                        Integer.MIN_VALUE);
-                isMultiuser = rInfo.serviceInfo.metaData.getBoolean(EXTRA_SERVICE_IS_MULTIUSER);
+            if (D) {
+                Log.d(mTag, String.format("bindBestPackage for %s : %s found %d, %s", mAction,
+                        (justCheckThisPackage == null ? ""
+                                : "(" + justCheckThisPackage + ") "), rInfos.size(),
+                        (bestPackage == null ? "no new best package"
+                                : "new best package: " + bestPackage)));
             }
-
-            if (version > mVersion) {
-                bestVersion = version;
-                bestPackage = packageName;
-                bestIsMultiuser = isMultiuser;
-            }
+        } else {
+            if (D) Log.d(mTag, "Unable to query intent services for action: " + mAction);
         }
-
-        if (D) Log.d(mTag, String.format("bindBestPackage for %s : %s found %d, %s", mAction,
-                (justCheckThisPackage == null ? "" : "(" + justCheckThisPackage + ") "),
-                rInfos.size(),
-                (bestPackage == null ? "no new best package" : "new best package: "
-                + bestPackage)));
-
         if (bestPackage != null) {
             bindToPackageLocked(bestPackage, bestVersion, bestIsMultiuser);
             return true;
@@ -243,8 +280,9 @@
                     // package updated, make sure to rebind
                     unbindLocked();
                 }
-                // check the updated package in case it is better
-                bindBestPackageLocked(packageName);
+                // Need to check all packages because this method is also called when a
+                // system app is uninstalled and the stock version in reinstalled.
+                bindBestPackageLocked(null);
             }
         }
 
@@ -256,7 +294,7 @@
                     unbindLocked();
                 }
                 // check the new package is case it is better
-                bindBestPackageLocked(packageName);
+                bindBestPackageLocked(null);
             }
         }
 
@@ -271,6 +309,20 @@
                 }
             }
         }
+
+        @Override
+        public boolean onPackageChanged(String packageName, int uid, String[] components) {
+            synchronized (mLock) {
+                if (packageName.equals(mPackageName)) {
+                    // service enabled or disabled, make sure to rebind
+                    unbindLocked();
+                }
+                // the service might be disabled, need to search for a new
+                // package
+                bindBestPackageLocked(null);
+            }
+            return super.onPackageChanged(packageName, uid, components);
+        }
     };
 
     @Override
@@ -323,7 +375,7 @@
         synchronized (mLock) {
             if (!mIsMultiuser) {
                 unbindLocked();
-                bindBestPackageLocked(null);
+                bindBestPackageLocked(mServicePackageName);
             }
         }
     }
diff --git a/services/java/com/android/server/location/GeocoderProxy.java b/services/java/com/android/server/location/GeocoderProxy.java
index 8103695..5d4a770 100644
--- a/services/java/com/android/server/location/GeocoderProxy.java
+++ b/services/java/com/android/server/location/GeocoderProxy.java
@@ -40,8 +40,10 @@
     private final ServiceWatcher mServiceWatcher;
 
     public static GeocoderProxy createAndBind(Context context,
-            List<String> initialPackageNames, Handler handler) {
-        GeocoderProxy proxy = new GeocoderProxy(context, initialPackageNames, handler);
+            int overlaySwitchResId, int defaultServicePackageNameResId,
+            int initialPackageNamesResId, Handler handler) {
+        GeocoderProxy proxy = new GeocoderProxy(context, overlaySwitchResId,
+            defaultServicePackageNameResId, initialPackageNamesResId, handler);
         if (proxy.bind()) {
             return proxy;
         } else {
@@ -49,11 +51,13 @@
         }
     }
 
-    public GeocoderProxy(Context context, List<String> initialPackageNames, Handler handler) {
+    private GeocoderProxy(Context context,
+            int overlaySwitchResId, int defaultServicePackageNameResId,
+            int initialPackageNamesResId, Handler handler) {
         mContext = context;
 
-        mServiceWatcher = new ServiceWatcher(mContext, TAG, SERVICE_ACTION, initialPackageNames,
-                null, handler);
+        mServiceWatcher = new ServiceWatcher(mContext, TAG, SERVICE_ACTION, overlaySwitchResId,
+            defaultServicePackageNameResId, initialPackageNamesResId, null, handler);
     }
 
     private boolean bind () {
diff --git a/services/java/com/android/server/location/GeofenceProxy.java b/services/java/com/android/server/location/GeofenceProxy.java
index 36e9fcc..f6be27b 100644
--- a/services/java/com/android/server/location/GeofenceProxy.java
+++ b/services/java/com/android/server/location/GeofenceProxy.java
@@ -59,8 +59,10 @@
     };
 
     public static GeofenceProxy createAndBind(Context context,
-            List<String> initialPackageNames, Handler handler, IGpsGeofenceHardware gpsGeofence) {
-        GeofenceProxy proxy = new GeofenceProxy(context, initialPackageNames, handler, gpsGeofence);
+            int overlaySwitchResId, int defaultServicePackageNameResId,
+            int initialPackageNamesResId, Handler handler, IGpsGeofenceHardware gpsGeofence) {
+        GeofenceProxy proxy = new GeofenceProxy(context, overlaySwitchResId,
+            defaultServicePackageNameResId, initialPackageNamesResId, handler, gpsGeofence);
         if (proxy.bindGeofenceProvider()) {
             return proxy;
         } else {
@@ -68,11 +70,12 @@
         }
     }
 
-    private GeofenceProxy(Context context, List<String> initialPackageName, Handler handler,
-            IGpsGeofenceHardware gpsGeofence) {
+    private GeofenceProxy(Context context,
+            int overlaySwitchResId, int defaultServicePackageNameResId,
+            int initialPackageNamesResId, Handler handler, IGpsGeofenceHardware gpsGeofence) {
         mContext = context;
-        mServiceWatcher = new ServiceWatcher(context, TAG, SERVICE_ACTION, initialPackageName,
-                mRunnable, handler);
+        mServiceWatcher = new ServiceWatcher(context, TAG, SERVICE_ACTION, overlaySwitchResId,
+            defaultServicePackageNameResId, initialPackageNamesResId, mRunnable, handler);
         mGpsGeofenceHardware = gpsGeofence;
         bindHardwareGeofence();
     }
diff --git a/services/java/com/android/server/location/LocationProviderProxy.java b/services/java/com/android/server/location/LocationProviderProxy.java
index 7faf72c..14db862 100644
--- a/services/java/com/android/server/location/LocationProviderProxy.java
+++ b/services/java/com/android/server/location/LocationProviderProxy.java
@@ -53,10 +53,13 @@
     private ProviderRequest mRequest = null;
     private WorkSource mWorksource = new WorkSource();
 
-    public static LocationProviderProxy createAndBind(Context context, String name, String action,
-            List<String> initialPackageNames, Handler handler) {
+    public static LocationProviderProxy createAndBind(
+            Context context, String name, String action,
+            int overlaySwitchResId, int defaultServicePackageNameResId,
+            int initialPackageNamesResId, Handler handler) {
         LocationProviderProxy proxy = new LocationProviderProxy(context, name, action,
-                initialPackageNames, handler);
+                overlaySwitchResId, defaultServicePackageNameResId, initialPackageNamesResId,
+                handler);
         if (proxy.bind()) {
             return proxy;
         } else {
@@ -65,10 +68,12 @@
     }
 
     private LocationProviderProxy(Context context, String name, String action,
-            List<String> initialPackageNames, Handler handler) {
+            int overlaySwitchResId, int defaultServicePackageNameResId,
+            int initialPackageNamesResId, Handler handler) {
         mContext = context;
         mName = name;
-        mServiceWatcher = new ServiceWatcher(mContext, TAG, action, initialPackageNames,
+        mServiceWatcher = new ServiceWatcher(mContext, TAG + "-" + name, action, overlaySwitchResId,
+                defaultServicePackageNameResId, initialPackageNamesResId,
                 mNewServiceWork, handler);
     }