Add background location throttling to LocationManagerService

Test: manual tests
Change-Id: I27f060d2f5338a8750dcbe5cbe4cfadb4edb0464
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index c9b59ade..6cc72de 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import android.app.ActivityManager;
 import android.content.pm.PackageManagerInternal;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.location.ProviderProperties;
@@ -138,6 +139,9 @@
     // The maximum interval a location request can have and still be considered "high power".
     private static final long HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000;
 
+    // default background throttling interval if not overriden in settings
+    private static final long DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS = 30 * 1000;
+
     // Location Providers may sometimes deliver location updates
     // slightly faster that requested - provide grace period so
     // we don't unnecessarily filter events that are otherwise on
@@ -157,6 +161,7 @@
     private GeofenceManager mGeofenceManager;
     private PackageManager mPackageManager;
     private PowerManager mPowerManager;
+    private ActivityManager mActivityManager;
     private UserManager mUserManager;
     private GeocoderProxy mGeocodeProvider;
     private IGnssStatusProvider mGnssStatusProvider;
@@ -171,47 +176,47 @@
     // --- fields below are protected by mLock ---
     // Set of providers that are explicitly enabled
     // Only used by passive, fused & test.  Network & GPS are controlled separately, and not listed.
-    private final Set<String> mEnabledProviders = new HashSet<String>();
+    private final Set<String> mEnabledProviders = new HashSet<>();
 
     // Set of providers that are explicitly disabled
-    private final Set<String> mDisabledProviders = new HashSet<String>();
+    private final Set<String> mDisabledProviders = new HashSet<>();
 
     // Mock (test) providers
     private final HashMap<String, MockProvider> mMockProviders =
-            new HashMap<String, MockProvider>();
+            new HashMap<>();
 
     // all receivers
-    private final HashMap<Object, Receiver> mReceivers = new HashMap<Object, Receiver>();
+    private final HashMap<Object, Receiver> mReceivers = new HashMap<>();
 
     // currently installed providers (with mocks replacing real providers)
     private final ArrayList<LocationProviderInterface> mProviders =
-            new ArrayList<LocationProviderInterface>();
+            new ArrayList<>();
 
     // real providers, saved here when mocked out
     private final HashMap<String, LocationProviderInterface> mRealProviders =
-            new HashMap<String, LocationProviderInterface>();
+            new HashMap<>();
 
     // mapping from provider name to provider
     private final HashMap<String, LocationProviderInterface> mProvidersByName =
-            new HashMap<String, LocationProviderInterface>();
+            new HashMap<>();
 
     // mapping from provider name to all its UpdateRecords
     private final HashMap<String, ArrayList<UpdateRecord>> mRecordsByProvider =
-            new HashMap<String, ArrayList<UpdateRecord>>();
+            new HashMap<>();
 
     private final LocationRequestStatistics mRequestStatistics = new LocationRequestStatistics();
 
     // mapping from provider name to last known location
-    private final HashMap<String, Location> mLastLocation = new HashMap<String, Location>();
+    private final HashMap<String, Location> mLastLocation = new HashMap<>();
 
     // same as mLastLocation, but is not updated faster than LocationFudger.FASTEST_INTERVAL_MS.
     // locations stored here are not fudged for coarse permissions.
     private final HashMap<String, Location> mLastLocationCoarseInterval =
-            new HashMap<String, Location>();
+            new HashMap<>();
 
     // all providers that operate over proxy, for authorizing incoming location
     private final ArrayList<LocationProviderProxy> mProxyProviders =
-            new ArrayList<LocationProviderProxy>();
+            new ArrayList<>();
 
     // current active user on the device - other users are denied location data
     private int mCurrentUserId = UserHandle.USER_SYSTEM;
@@ -252,6 +257,10 @@
             // fetch power manager
             mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
 
+            // fetch activity manager
+            mActivityManager
+                    = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+
             // prepare worker thread
             mLocationHandler = new LocationWorkerHandler(BackgroundThread.get().getLooper());
 
@@ -286,6 +295,40 @@
             };
             mPackageManager.addOnPermissionsChangeListener(permissionListener);
 
+            // listen for background/foreground changes
+            ActivityManager.OnUidImportanceListener uidImportanceListener
+                    = new ActivityManager.OnUidImportanceListener() {
+                @Override
+                public void onUidImportance(int uid, int importance) {
+                    boolean foreground = isImportanceForeground(importance);
+                    HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size());
+                    synchronized (mLock) {
+                        for (Map.Entry<String, ArrayList<UpdateRecord>> entry
+                                : mRecordsByProvider.entrySet()) {
+                            String provider = entry.getKey();
+                            for (UpdateRecord record : entry.getValue()) {
+                                if (record.mReceiver.mUid == uid
+                                        && record.mIsForegroundUid != foreground) {
+                                    if (D) Log.d(TAG, "request from uid " + uid + " is now "
+                                            + (foreground ? "foreground" : "background)"));
+                                    record.mIsForegroundUid = foreground;
+
+                                    if (!isThrottlingExemptLocked(record.mReceiver)) {
+                                        affectedProviders.add(provider);
+                                    }
+                                }
+                            }
+                        }
+                        for (String provider : affectedProviders) {
+                            applyRequirementsLocked(provider);
+                        }
+                    }
+
+                }
+            };
+            mActivityManager.addOnUidImportanceListener(uidImportanceListener,
+                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE);
+
             mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
             updateUserProfiles(mCurrentUserId);
 
@@ -305,6 +348,17 @@
                         }
                     }
                 }, UserHandle.USER_ALL);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS),
+                true,
+                new ContentObserver(mLocationHandler) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        synchronized (mLock) {
+                            updateProvidersLocked();
+                        }
+                    }
+                }, UserHandle.USER_ALL);
         mPackageMonitor.register(mContext, mLocationHandler.getLooper(), true);
 
         // listen for user change
@@ -334,6 +388,10 @@
         }, UserHandle.ALL, intentFilter, null, mLocationHandler);
     }
 
+    private static boolean isImportanceForeground(int importance) {
+        return importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+    }
+
     /**
      * Provides a way for components held by the {@link LocationManagerService} to clean-up
      * gracefully on system's shutdown.
@@ -483,7 +541,7 @@
         that matches the signature of at least one package on this list.
         */
         Resources resources = mContext.getResources();
-        ArrayList<String> providerPackageNames = new ArrayList<String>();
+        ArrayList<String> providerPackageNames = new ArrayList<>();
         String[] pkgs = resources.getStringArray(
                 com.android.internal.R.array.config_locationProviderPackageNames);
         if (D) Log.d(TAG, "certificates for location providers pulled from: " +
@@ -651,7 +709,7 @@
         final boolean mHideFromAppOps; // True if AppOps should not monitor this receiver.
         final Object mKey;
 
-        final HashMap<String,UpdateRecord> mUpdateRecords = new HashMap<String,UpdateRecord>();
+        final HashMap<String,UpdateRecord> mUpdateRecords = new HashMap<>();
 
         // True if app ops has started monitoring this receiver for locations.
         boolean mOpMonitoring;
@@ -691,10 +749,7 @@
 
         @Override
         public boolean equals(Object otherObj) {
-            if (otherObj instanceof Receiver) {
-                return mKey.equals(((Receiver)otherObj).mKey);
-            }
-            return false;
+            return (otherObj instanceof Receiver) && mKey.equals(((Receiver) otherObj).mKey);
         }
 
         @Override
@@ -1011,13 +1066,25 @@
         mProvidersByName.remove(provider.getName());
     }
 
+    private boolean isOverlayProviderPackageLocked(String packageName) {
+        for (LocationProviderInterface provider : mProviders) {
+            if (provider instanceof LocationProviderProxy) {
+                if (packageName.equals(
+                        ((LocationProviderProxy) provider).getConnectedPackageName())) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
     /**
      * Returns "true" if access to the specified location provider is allowed by the current
      * user's settings. Access to all location providers is forbidden to non-location-provider
      * processes belonging to background users.
      *
      * @param provider the name of the location provider
-     * @return
      */
     private boolean isAllowedByCurrentUserSettingsLocked(String provider) {
         if (mEnabledProviders.contains(provider)) {
@@ -1039,7 +1106,6 @@
      *
      * @param provider the name of the location provider
      * @param uid the requestor's UID
-     * @return
      */
     private boolean isAllowedByUserSettingsLocked(String provider, int uid) {
         if (!isCurrentProfile(UserHandle.getUserId(uid)) && !isUidALocationProvider(uid)) {
@@ -1197,11 +1263,7 @@
             }
         }
 
-        if (getAllowedResolutionLevel(pid, uid) < allowedResolutionLevel) {
-            return false;
-        }
-
-        return true;
+        return getAllowedResolutionLevel(pid, uid) >= allowedResolutionLevel;
     }
 
     boolean checkLocationAccess(int pid, int uid, String packageName, int allowedResolutionLevel) {
@@ -1212,11 +1274,7 @@
             }
         }
 
-        if (getAllowedResolutionLevel(pid, uid) < allowedResolutionLevel) {
-            return false;
-        }
-
-        return true;
+        return getAllowedResolutionLevel(pid, uid) >= allowedResolutionLevel;
     }
 
     /**
@@ -1228,7 +1286,7 @@
     public List<String> getAllProviders() {
         ArrayList<String> out;
         synchronized (mLock) {
-            out = new ArrayList<String>(mProviders.size());
+            out = new ArrayList<>(mProviders.size());
             for (LocationProviderInterface provider : mProviders) {
                 String name = provider.getName();
                 if (LocationManager.FUSED_PROVIDER.equals(name)) {
@@ -1251,11 +1309,11 @@
     public List<String> getProviders(Criteria criteria, boolean enabledOnly) {
         int allowedResolutionLevel = getCallerAllowedResolutionLevel();
         ArrayList<String> out;
-        int uid = Binder.getCallingUid();;
+        int uid = Binder.getCallingUid();
         long identity = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                out = new ArrayList<String>(mProviders.size());
+                out = new ArrayList<>(mProviders.size());
                 for (LocationProviderInterface provider : mProviders) {
                     String name = provider.getName();
                     if (LocationManager.FUSED_PROVIDER.equals(name)) {
@@ -1370,14 +1428,12 @@
 
         ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
         if (records != null) {
-            final int N = records.size();
-            for (int i = 0; i < N; i++) {
-                UpdateRecord record = records.get(i);
+            for (UpdateRecord record : records) {
                 if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mUid))) {
                     // Sends a notification message to the receiver
                     if (!record.mReceiver.callProviderEnabledLocked(provider, enabled)) {
                         if (deadReceivers == null) {
-                            deadReceivers = new ArrayList<Receiver>();
+                            deadReceivers = new ArrayList<>();
                         }
                         deadReceivers.add(record.mReceiver);
                     }
@@ -1410,6 +1466,12 @@
         WorkSource worksource = new WorkSource();
         ProviderRequest providerRequest = new ProviderRequest();
 
+        ContentResolver resolver = mContext.getContentResolver();
+        long backgroundThrottleInterval = Settings.Global.getLong(
+                resolver,
+                Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
+                DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS);
+
         if (records != null) {
             for (UpdateRecord record : records) {
                 if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mUid))) {
@@ -1419,10 +1481,22 @@
                             record.mReceiver.mPackageName,
                             record.mReceiver.mAllowedResolutionLevel)) {
                         LocationRequest locationRequest = record.mRequest;
+                        long interval = locationRequest.getInterval();
+
+                        if (!isThrottlingExemptLocked(record.mReceiver)) {
+                            if (!record.mIsForegroundUid) {
+                                interval = Math.max(interval, backgroundThrottleInterval);
+                            }
+                            if (interval != locationRequest.getInterval()) {
+                                locationRequest = new LocationRequest(locationRequest);
+                                locationRequest.setInterval(interval);
+                            }
+                        }
+
                         providerRequest.locationRequests.add(locationRequest);
-                        if (locationRequest.getInterval() < providerRequest.interval) {
+                        if (interval < providerRequest.interval) {
                             providerRequest.reportLocation = true;
-                            providerRequest.interval = locationRequest.getInterval();
+                            providerRequest.interval = interval;
                         }
                     }
                 }
@@ -1468,10 +1542,15 @@
         p.setRequest(providerRequest, worksource);
     }
 
+    private boolean isThrottlingExemptLocked(Receiver recevier) {
+        return isOverlayProviderPackageLocked(recevier.mPackageName);
+    }
+
     private class UpdateRecord {
         final String mProvider;
         final LocationRequest mRequest;
         final Receiver mReceiver;
+        boolean mIsForegroundUid;
         Location mLastFixBroadcast;
         long mLastStatusBroadcast;
 
@@ -1482,10 +1561,12 @@
             mProvider = provider;
             mRequest = request;
             mReceiver = receiver;
+            mIsForegroundUid = isImportanceForeground(
+                    mActivityManager.getPackageImportance(mReceiver.mPackageName));
 
             ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
             if (records == null) {
-                records = new ArrayList<UpdateRecord>();
+                records = new ArrayList<>();
                 mRecordsByProvider.put(provider, records);
             }
             if (!records.contains(this)) {
@@ -1517,7 +1598,7 @@
                 receiverRecords.remove(this.mProvider);
 
                 // and also remove the Receiver if it has no more update records
-                if (removeReceiver && receiverRecords.size() == 0) {
+                if (receiverRecords.size() == 0) {
                     removeUpdatesLocked(mReceiver);
                 }
             }
@@ -1525,14 +1606,9 @@
 
         @Override
         public String toString() {
-            StringBuilder s = new StringBuilder();
-            s.append("UpdateRecord[");
-            s.append(mProvider);
-            s.append(' ').append(mReceiver.mPackageName).append('(');
-            s.append(mReceiver.mUid).append(')');
-            s.append(' ').append(mRequest);
-            s.append(']');
-            return s.toString();
+            return "UpdateRecord[" + mProvider + " " + mReceiver.mPackageName
+                    + "(" + mReceiver.mUid + (mIsForegroundUid ? " foreground" : " background")
+                    + ")" + " " + mRequest + "]";
         }
     }
 
@@ -1681,14 +1757,17 @@
             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 exist: " + name);
         }
 
         UpdateRecord record = new UpdateRecord(name, request, receiver);
+        if (D) Log.d(TAG, "request " + Integer.toHexString(System.identityHashCode(receiver))
+                + " " + name + " " + request + " from " + packageName + "(" + uid + " "
+                + (record.mIsForegroundUid ? "foreground" : "background")
+                + (isOverlayProviderPackageLocked(receiver.mPackageName) ? " [whitelisted]" : "") + ")");
+
         UpdateRecord oldRecord = receiver.mUpdateRecords.put(name, record);
         if (oldRecord != null) {
             oldRecord.disposeLocked(false);
@@ -1743,7 +1822,7 @@
         receiver.updateMonitoring(false);
 
         // Record which providers were associated with this listener
-        HashSet<String> providers = new HashSet<String>();
+        HashSet<String> providers = new HashSet<>();
         HashMap<String, UpdateRecord> oldRecords = receiver.mUpdateRecords;
         if (oldRecords != null) {
             // Call dispose() on the obsolete update records.
@@ -2084,9 +2163,7 @@
         try {
             synchronized (mLock) {
                 LocationProviderInterface p = mProvidersByName.get(provider);
-                if (p == null) return false;
-
-                return isAllowedByUserSettingsLocked(provider, uid);
+                return p != null && isAllowedByUserSettingsLocked(provider, uid);
             }
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -2197,11 +2274,7 @@
         }
 
         // Check whether the expiry date has passed
-        if (record.mRequest.getExpireAt() < now) {
-            return false;
-        }
-
-        return true;
+        return record.mRequest.getExpireAt() >= now;
     }
 
     private void handleLocationChangedLocked(Location location, boolean passive) {
@@ -2216,7 +2289,7 @@
 
         // Update last known locations
         Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
-        Location lastNoGPSLocation = null;
+        Location lastNoGPSLocation;
         Location lastLocation = mLastLocation.get(provider);
         if (lastLocation == null) {
             lastLocation = new Location(provider);
@@ -2296,7 +2369,7 @@
                 continue;
             }
 
-            Location notifyLocation = null;
+            Location notifyLocation;
             if (receiver.mAllowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
                 notifyLocation = coarseLocation;  // use coarse location
             } else {
@@ -2333,14 +2406,14 @@
             // track expired records
             if (r.mRequest.getNumUpdates() <= 0 || r.mRequest.getExpireAt() < now) {
                 if (deadUpdateRecords == null) {
-                    deadUpdateRecords = new ArrayList<UpdateRecord>();
+                    deadUpdateRecords = new ArrayList<>();
                 }
                 deadUpdateRecords.add(r);
             }
             // track dead receivers
             if (receiverDead) {
                 if (deadReceivers == null) {
-                    deadReceivers = new ArrayList<Receiver>();
+                    deadReceivers = new ArrayList<>();
                 }
                 if (!deadReceivers.contains(receiver)) {
                     deadReceivers.add(receiver);
@@ -2417,7 +2490,7 @@
                 for (Receiver receiver : mReceivers.values()) {
                     if (receiver.mPackageName.equals(packageName)) {
                         if (deadReceivers == null) {
-                            deadReceivers = new ArrayList<Receiver>();
+                            deadReceivers = new ArrayList<>();
                         }
                         deadReceivers.add(receiver);
                     }
@@ -2694,6 +2767,13 @@
                     pw.println("      " + record);
                 }
             }
+            pw.println("  Overlay Provider Packages:");
+            for (LocationProviderInterface provider : mProviders) {
+                if (provider instanceof LocationProviderProxy) {
+                    pw.println("    " + provider.getName() + ": "
+                            + ((LocationProviderProxy) provider).getConnectedPackageName());
+                }
+            }
             pw.println("  Historical Records by Provider:");
             for (Map.Entry<PackageProviderKey, PackageStatistics> entry
                     : mRequestStatistics.statistics.entrySet()) {