Duty Cycling and Low Power Mode GNSS feature implementations

Implementaion of 2 GNSS Android-P features:
- The Duty Cycling API to enable high accuracy applications development
- The Low Power Mode GNSS API to save power when indoor

Bug: 64009176
Test: Existing unit tests still pass.
Change-Id: I3ba3b86a635a54927c694fdd66a038757e843937
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index bdfccd6e..0a86281 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.location.ProviderRequest;
@@ -147,7 +148,7 @@
     private static final long HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000;
 
     private static final int FOREGROUND_IMPORTANCE_CUTOFF
-        = ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+            = ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
 
     // default background throttling interval if not overriden in settings
     private static final long DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS = 30 * 60 * 1000;
@@ -239,7 +240,7 @@
 
     // current active user on the device - other users are denied location data
     private int mCurrentUserId = UserHandle.USER_SYSTEM;
-    private int[] mCurrentUserProfiles = new int[] { UserHandle.USER_SYSTEM };
+    private int[] mCurrentUserProfiles = new int[]{UserHandle.USER_SYSTEM};
 
     private GnssLocationProvider.GnssSystemInfoProvider mGnssSystemInfoProvider;
 
@@ -253,7 +254,7 @@
     public LocationManagerService(Context context) {
         super();
         mContext = context;
-        mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+        mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
 
         // Let the package manager query which are the default location
         // providers as they get certain permissions granted by default.
@@ -370,18 +371,18 @@
                     }
                 }, UserHandle.USER_ALL);
         mContext.getContentResolver().registerContentObserver(
-            Settings.Global.getUriFor(
-                Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST),
-            true,
-            new ContentObserver(mLocationHandler) {
-                @Override
-                public void onChange(boolean selfChange) {
-                    synchronized (mLock) {
-                        updateBackgroundThrottlingWhitelistLocked();
-                        updateProvidersLocked();
+                Settings.Global.getUriFor(
+                        Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST),
+                true,
+                new ContentObserver(mLocationHandler) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        synchronized (mLock) {
+                            updateBackgroundThrottlingWhitelistLocked();
+                            updateProvidersLocked();
+                        }
                     }
-                }
-            }, UserHandle.USER_ALL);
+                }, UserHandle.USER_ALL);
         mPackageMonitor.register(mContext, mLocationHandler.getLooper(), true);
 
         // listen for user change
@@ -402,7 +403,7 @@
                     updateUserProfiles(mCurrentUserId);
                 } else if (Intent.ACTION_SHUTDOWN.equals(action)) {
                     // shutdown only if UserId indicates whole system, not just one user
-                    if(D) Log.d(TAG, "Shutdown received with UserId: " + getSendingUserId());
+                    if (D) Log.d(TAG, "Shutdown received with UserId: " + getSendingUserId());
                     if (getSendingUserId() == UserHandle.USER_ALL) {
                         shutdownComponents();
                     }
@@ -416,13 +417,15 @@
         HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size());
         synchronized (mLock) {
             for (Entry<String, ArrayList<UpdateRecord>> entry
-                : mRecordsByProvider.entrySet()) {
+                    : mRecordsByProvider.entrySet()) {
                 String provider = entry.getKey();
                 for (UpdateRecord record : entry.getValue()) {
                     if (record.mReceiver.mIdentity.mUid == uid
-                        && record.mIsForegroundUid != foreground) {
-                        if (D) Log.d(TAG, "request from uid " + uid + " is now "
-                            + (foreground ? "foreground" : "background)"));
+                            && record.mIsForegroundUid != foreground) {
+                        if (D) {
+                            Log.d(TAG, "request from uid " + uid + " is now "
+                                    + (foreground ? "foreground" : "background)"));
+                        }
                         record.mIsForegroundUid = foreground;
 
                         if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) {
@@ -436,10 +439,12 @@
             }
 
             for (Entry<IGnssMeasurementsListener, Identity> entry
-                : mGnssMeasurementsListeners.entrySet()) {
+                    : mGnssMeasurementsListeners.entrySet()) {
                 if (entry.getValue().mUid == uid) {
-                    if (D) Log.d(TAG, "gnss measurements listener from uid " + uid
-                        + " is now " + (foreground ? "foreground" : "background)"));
+                    if (D) {
+                        Log.d(TAG, "gnss measurements listener from uid " + uid
+                                + " is now " + (foreground ? "foreground" : "background)"));
+                    }
                     if (foreground || isThrottlingExemptLocked(entry.getValue())) {
                         mGnssMeasurementsProvider.addListener(entry.getKey());
                     } else {
@@ -449,11 +454,13 @@
             }
 
             for (Entry<IGnssNavigationMessageListener, Identity> entry
-                : mGnssNavigationMessageListeners.entrySet()) {
+                    : mGnssNavigationMessageListeners.entrySet()) {
                 if (entry.getValue().mUid == uid) {
-                    if (D) Log.d(TAG, "gnss navigation message listener from uid "
-                        + uid + " is now "
-                        + (foreground ? "foreground" : "background)"));
+                    if (D) {
+                        Log.d(TAG, "gnss navigation message listener from uid "
+                                + uid + " is now "
+                                + (foreground ? "foreground" : "background)"));
+                    }
                     if (foreground || isThrottlingExemptLocked(entry.getValue())) {
                         mGnssNavigationMessageProvider.addListener(entry.getKey());
                     } else {
@@ -477,7 +484,7 @@
      * support for components that do not wish to handle such event.
      */
     private void shutdownComponents() {
-        if(D) Log.d(TAG, "Shutting down components...");
+        if (D) Log.d(TAG, "Shutting down components...");
 
         LocationProviderInterface gpsProvider = mProvidersByName.get(LocationManager.GPS_PROVIDER);
         if (gpsProvider != null && gpsProvider.isEnabled()) {
@@ -563,8 +570,10 @@
                 // as a proxy for coreApp="true"
                 if (pm.checkSignatures(systemPackageName, packageName)
                         != PackageManager.SIGNATURE_MATCH) {
-                    if (D) Log.d(TAG, "Fallback candidate not signed the same as system: "
-                            + packageName);
+                    if (D) {
+                        Log.d(TAG, "Fallback candidate not signed the same as system: "
+                                + packageName);
+                    }
                     continue;
                 }
 
@@ -622,8 +631,10 @@
         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: " +
-                Arrays.toString(pkgs));
+        if (D) {
+            Log.d(TAG, "certificates for location providers pulled from: " +
+                    Arrays.toString(pkgs));
+        }
         if (pkgs != null) providerPackageNames.addAll(Arrays.asList(pkgs));
 
         ensureFallbackFusedProviderPresentLocked(providerPackageNames);
@@ -642,7 +653,7 @@
             mProxyProviders.add(networkProvider);
             addProviderLocked(networkProvider);
         } else {
-            Slog.w(TAG,  "no network location provider found");
+            Slog.w(TAG, "no network location provider found");
         }
 
         // bind to fused provider
@@ -671,7 +682,7 @@
                 com.android.internal.R.array.config_locationProviderPackageNames,
                 mLocationHandler);
         if (mGeocodeProvider == null) {
-            Slog.e(TAG,  "no geocoder provider found");
+            Slog.e(TAG, "no geocoder provider found");
         }
 
         // bind to fused hardware provider if supported
@@ -697,14 +708,14 @@
 
         // bind to geofence provider
         GeofenceProxy provider = GeofenceProxy.createAndBind(
-                mContext,com.android.internal.R.bool.config_enableGeofenceOverlay,
+                mContext, com.android.internal.R.bool.config_enableGeofenceOverlay,
                 com.android.internal.R.string.config_geofenceProviderPackageName,
                 com.android.internal.R.array.config_locationProviderPackageNames,
                 mLocationHandler,
                 mGpsGeofenceProxy,
                 flpHardwareProvider != null ? flpHardwareProvider.getGeofenceHardware() : null);
         if (provider == null) {
-            Slog.d(TAG,  "Unable to bind FLP Geofence proxy.");
+            Slog.d(TAG, "Unable to bind FLP Geofence proxy.");
         }
 
         // bind to hardware activity recognition
@@ -751,6 +762,7 @@
 
     /**
      * Called when the device's active user changes.
+     *
      * @param userId the new active user's UserId
      */
     private void switchUser(int userId) {
@@ -797,7 +809,7 @@
         final boolean mHideFromAppOps; // True if AppOps should not monitor this receiver.
         final Object mKey;
 
-        final HashMap<String,UpdateRecord> mUpdateRecords = new HashMap<>();
+        final HashMap<String, UpdateRecord> mUpdateRecords = new HashMap<>();
 
         // True if app ops has started monitoring this receiver for locations.
         boolean mOpMonitoring;
@@ -914,9 +926,9 @@
         /**
          * Update AppOps monitoring for a single location request and op type.
          *
-         * @param allowMonitoring True if monitoring is allowed for this request/op.
+         * @param allowMonitoring     True if monitoring is allowed for this request/op.
          * @param currentlyMonitoring True if AppOps is currently monitoring this request/op.
-         * @param op AppOps code for the op to update.
+         * @param op                  AppOps code for the op to update.
          * @return True if monitoring is on for this request/op after updating.
          */
         private boolean updateMonitoring(boolean allowMonitoring, boolean currentlyMonitoring,
@@ -1004,7 +1016,8 @@
                 }
             } else {
                 Intent locationChanged = new Intent();
-                locationChanged.putExtra(LocationManager.KEY_LOCATION_CHANGED, new Location(location));
+                locationChanged.putExtra(LocationManager.KEY_LOCATION_CHANGED,
+                        new Location(location));
                 try {
                     synchronized (this) {
                         // synchronize to ensure incrementPendingBroadcastsLocked()
@@ -1286,7 +1299,7 @@
         }
 
         if (mGnssBatchingProvider != null) {
-             mGnssBatchingProvider.flush();
+            mGnssBatchingProvider.flush();
         }
     }
 
@@ -1301,7 +1314,7 @@
         if (mGnssBatchingProvider != null) {
             mGnssBatchingInProgress = false;
             return mGnssBatchingProvider.stop();
-        } else  {
+        } else {
             return false;
         }
     }
@@ -1363,7 +1376,7 @@
      * processes belonging to background users.
      *
      * @param provider the name of the location provider
-     * @param uid the requestor's UID
+     * @param uid      the requestor's UID
      */
     private boolean isAllowedByUserSettingsLocked(String provider, int uid) {
         if (!isCurrentProfile(UserHandle.getUserId(uid)) && !isUidALocationProvider(uid)) {
@@ -1467,7 +1480,7 @@
      * location provider.
      *
      * @param allowedResolutionLevel resolution level allowed to caller
-     * @param providerName the name of the location provider
+     * @param providerName           the name of the location provider
      */
     private void checkResolutionLevelIsSufficientForProviderUse(int allowedResolutionLevel,
             String providerName) {
@@ -1718,7 +1731,9 @@
                 resolver,
                 Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
                 DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS);
+        // initialize the low power mode to true and set to false if any of the records requires
 
+        providerRequest.lowPowerMode = true;
         if (records != null) {
             for (UpdateRecord record : records) {
                 if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
@@ -1742,6 +1757,9 @@
 
                         record.mRequest = locationRequest;
                         providerRequest.locationRequests.add(locationRequest);
+                        if (!locationRequest.isLowPowerMode()) {
+                            providerRequest.lowPowerMode = false;
+                        }
                         if (interval < providerRequest.interval) {
                             providerRequest.reportLocation = true;
                             providerRequest.interval = interval;
@@ -1794,23 +1812,23 @@
     public String[] getBackgroundThrottlingWhitelist() {
         synchronized (mLock) {
             return mBackgroundThrottlePackageWhitelist.toArray(
-                new String[mBackgroundThrottlePackageWhitelist.size()]);
+                    new String[mBackgroundThrottlePackageWhitelist.size()]);
         }
     }
 
     private void updateBackgroundThrottlingWhitelistLocked() {
         String setting = Settings.Global.getString(
-            mContext.getContentResolver(),
-            Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST);
+                mContext.getContentResolver(),
+                Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST);
         if (setting == null) {
             setting = "";
         }
 
         mBackgroundThrottlePackageWhitelist.clear();
         mBackgroundThrottlePackageWhitelist.addAll(
-            SystemConfig.getInstance().getAllowUnthrottledLocation());
+                SystemConfig.getInstance().getAllowUnthrottledLocation());
         mBackgroundThrottlePackageWhitelist.addAll(
-            Arrays.asList(setting.split(",")));
+                Arrays.asList(setting.split(",")));
     }
 
     private boolean isThrottlingExemptLocked(Identity identity) {
@@ -1894,7 +1912,8 @@
         @Override
         public String toString() {
             return "UpdateRecord[" + mProvider + " " + mReceiver.mIdentity.mPackageName
-                    + "(" + mReceiver.mIdentity.mUid + (mIsForegroundUid ? " foreground" : " background")
+                    + "(" + mReceiver.mIdentity.mUid + (mIsForegroundUid ? " foreground"
+                    : " background")
                     + ")" + " " + mRealRequest + "]";
         }
     }
@@ -1936,8 +1955,13 @@
      * @return a version of request that meets the given resolution and consistency requirements
      * @hide
      */
-    private LocationRequest createSanitizedRequest(LocationRequest request, int resolutionLevel) {
+    private LocationRequest createSanitizedRequest(LocationRequest request, int resolutionLevel,
+            boolean callerHasLocationHardwarePermission) {
         LocationRequest sanitizedRequest = new LocationRequest(request);
+        if (!callerHasLocationHardwarePermission) {
+            // allow setting low power mode only for callers with location hardware permission
+            sanitizedRequest.setLowPowerMode(false);
+        }
         if (resolutionLevel < RESOLUTION_LEVEL_FINE) {
             switch (sanitizedRequest.getQuality()) {
                 case LocationRequest.ACCURACY_FINE:
@@ -2013,7 +2037,11 @@
         if (hideFromAppOps) {
             checkUpdateAppOpsAllowed();
         }
-        LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel);
+        boolean callerHasLocationHardwarePermission =
+                mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
+                        == PackageManager.PERMISSION_GRANTED;
+        LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel,
+                callerHasLocationHardwarePermission);
 
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
@@ -2050,11 +2078,13 @@
         }
 
         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")
-                + (isThrottlingExemptLocked(receiver.mIdentity)
+        if (D) {
+            Log.d(TAG, "request " + Integer.toHexString(System.identityHashCode(receiver))
+                    + " " + name + " " + request + " from " + packageName + "(" + uid + " "
+                    + (record.mIsForegroundUid ? "foreground" : "background")
+                    + (isThrottlingExemptLocked(receiver.mIdentity)
                     ? " [whitelisted]" : "") + ")");
+        }
 
         UpdateRecord oldRecord = receiver.mUpdateRecords.put(name, record);
         if (oldRecord != null) {
@@ -2159,14 +2189,18 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             if (mBlacklist.isBlacklisted(packageName)) {
-                if (D) Log.d(TAG, "not returning last loc for blacklisted app: " +
-                        packageName);
+                if (D) {
+                    Log.d(TAG, "not returning last loc for blacklisted app: " +
+                            packageName);
+                }
                 return null;
             }
 
             if (!reportLocationAccessNoThrow(pid, uid, packageName, allowedResolutionLevel)) {
-                if (D) Log.d(TAG, "not returning last loc for no op app: " +
-                        packageName);
+                if (D) {
+                    Log.d(TAG, "not returning last loc for no op app: " +
+                            packageName);
+                }
                 return null;
             }
 
@@ -2192,7 +2226,8 @@
                     return null;
                 }
                 if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
-                    Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
+                    Location noGPSLocation = location.getExtraLocation(
+                            Location.EXTRA_NO_GPS_LOCATION);
                     if (noGPSLocation != null) {
                         return new Location(mLocationFudger.getOrCreate(noGPSLocation));
                     }
@@ -2216,7 +2251,12 @@
         checkPackageName(packageName);
         checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,
                 request.getProvider());
-        LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel);
+        // Require that caller can manage given document
+        boolean callerHasLocationHardwarePermission =
+                mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
+                        == PackageManager.PERMISSION_GRANTED;
+        LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel,
+                callerHasLocationHardwarePermission);
 
         if (D) Log.d(TAG, "requestGeofence: " + sanitizedRequest + " " + geofence + " " + intent);
 
@@ -2282,8 +2322,7 @@
 
     @Override
     public boolean addGnssMeasurementsListener(
-            IGnssMeasurementsListener listener,
-            String packageName) {
+            IGnssMeasurementsListener listener, String packageName) {
         if (!hasGnssPermissions(packageName) || mGnssMeasurementsProvider == null) {
             return false;
         }
@@ -2296,7 +2335,7 @@
             try {
                 if (isThrottlingExemptLocked(callerIdentity)
                         || isImportanceForeground(
-                                mActivityManager.getPackageImportance(packageName))) {
+                        mActivityManager.getPackageImportance(packageName))) {
                     return mGnssMeasurementsProvider.addListener(listener);
                 }
             } finally {
@@ -2327,13 +2366,13 @@
 
         synchronized (mLock) {
             Identity callerIdentity
-                = new Identity(Binder.getCallingUid(), Binder.getCallingPid(), packageName);
+                    = new Identity(Binder.getCallingUid(), Binder.getCallingPid(), packageName);
             mGnssNavigationMessageListeners.put(listener, callerIdentity);
             long identity = Binder.clearCallingIdentity();
             try {
                 if (isThrottlingExemptLocked(callerIdentity)
                         || isImportanceForeground(
-                                mActivityManager.getPackageImportance(packageName))) {
+                        mActivityManager.getPackageImportance(packageName))) {
                     return mGnssNavigationMessageProvider.addListener(listener);
                 }
             } finally {
@@ -2394,7 +2433,7 @@
     /**
      * @return null if the provider does not exist
      * @throws SecurityException if the provider is not allowed to be
-     * accessed by the caller
+     *                           accessed by the caller
      */
     @Override
     public ProviderProperties getProviderProperties(String provider) {
@@ -2417,7 +2456,7 @@
     /**
      * @return null if the provider does not exist
      * @throws SecurityException if the provider is not allowed to be
-     * accessed by the caller
+     *                           accessed by the caller
      */
     @Override
     public String getNetworkProviderPackage() {
@@ -2641,8 +2680,10 @@
             }
 
             if (mBlacklist.isBlacklisted(receiver.mIdentity.mPackageName)) {
-                if (D) Log.d(TAG, "skipping loc update for blacklisted app: " +
-                        receiver.mIdentity.mPackageName);
+                if (D) {
+                    Log.d(TAG, "skipping loc update for blacklisted app: " +
+                            receiver.mIdentity.mPackageName);
+                }
                 continue;
             }
 
@@ -2651,8 +2692,10 @@
                     receiver.mIdentity.mUid,
                     receiver.mIdentity.mPackageName,
                     receiver.mAllowedResolutionLevel)) {
-                if (D) Log.d(TAG, "skipping loc update for no op app: " +
-                        receiver.mIdentity.mPackageName);
+                if (D) {
+                    Log.d(TAG, "skipping loc update for no op app: " +
+                            receiver.mIdentity.mPackageName);
+                }
                 continue;
             }
 
@@ -3114,12 +3157,12 @@
             }
 
             pw.append("  fudger: ");
-            mLocationFudger.dump(fd, pw,  args);
+            mLocationFudger.dump(fd, pw, args);
 
             if (args.length > 0 && "short".equals(args[0])) {
                 return;
             }
-            for (LocationProviderInterface provider: mProviders) {
+            for (LocationProviderInterface provider : mProviders) {
                 pw.print(provider.getName() + " Internal State");
                 if (provider instanceof LocationProviderProxy) {
                     LocationProviderProxy proxy = (LocationProviderProxy) provider;