Handle tri-state location permission.

Use noteOp instead of checkOp as checkOp doesn't consider
background status.

Watch location permission changes caused by background status.

Restrict age of last location for foreground-only apps.

Bug: 80318398
Test: Manual
Change-Id: I3499d4150478b6f501966953f8e4a08169f3557c
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 00180a8..e341363 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9032,6 +9032,14 @@
             "location_background_throttle_package_whitelist";
 
         /**
+         * Maximum staleness allowed for last location when returned to clients with only foreground
+         * location permissions.
+         * @hide
+         */
+        public static final String LOCATION_LAST_LOCATION_MAX_AGE_MILLIS =
+                "location_last_location_max_age_millis";
+
+        /**
         * Whether TV will switch to MHL port when a mobile device is plugged in.
         * (0 = false, 1 = true)
         * @hide
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index b7460ac..07606ea 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -272,6 +272,7 @@
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
                     Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
+                    Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS,
                     Settings.Global.LOCATION_GLOBAL_KILL_SWITCH,
                     Settings.Global.LOCATION_SETTINGS_LINK_TO_PERMISSIONS_ENABLED,
                     Settings.Global.LOCK_SOUND,
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index c5ab932..de02e81 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -153,6 +153,10 @@
     // default background throttling interval if not overriden in settings
     private static final long DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS = 30 * 60 * 1000;
 
+    // Default value for maximum age of last location returned to applications with foreground-only
+    // location permissions.
+    private static final long DEFAULT_LAST_LOCATION_MAX_AGE_MS = 20 * 60 * 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
@@ -241,6 +245,9 @@
     private int mCurrentUserId = UserHandle.USER_SYSTEM;
     private int[] mCurrentUserProfiles = new int[]{UserHandle.USER_SYSTEM};
 
+    // Maximum age of last location returned to clients with foreground-only location permissions.
+    private long mLastLocationMaxAgeMs;
+
     private GnssLocationProvider.GnssSystemInfoProvider mGnssSystemInfoProvider;
 
     private GnssLocationProvider.GnssMetricsProvider mGnssMetricsProvider;
@@ -308,7 +315,8 @@
                     }
                 }
             };
-            mAppOps.startWatchingMode(AppOpsManager.OP_COARSE_LOCATION, null, callback);
+            mAppOps.startWatchingMode(AppOpsManager.OP_COARSE_LOCATION, null,
+                    AppOpsManager.WATCH_FOREGROUND_CHANGES, callback);
 
             PackageManager.OnPermissionsChangedListener permissionListener
                     = new PackageManager.OnPermissionsChangedListener() {
@@ -341,6 +349,7 @@
             updateUserProfiles(mCurrentUserId);
 
             updateBackgroundThrottlingWhitelistLocked();
+            updateLastLocationMaxAgeLocked();
 
             // prepare providers
             loadProvidersLocked();
@@ -370,6 +379,18 @@
                     }
                 }, UserHandle.USER_ALL);
         mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS),
+                true,
+                new ContentObserver(mLocationHandler) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        synchronized (mLock) {
+                            updateLastLocationMaxAgeLocked();
+                        }
+                    }
+                }
+        );
+        mContext.getContentResolver().registerContentObserver(
                 Settings.Global.getUriFor(
                         Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST),
                 true,
@@ -382,6 +403,7 @@
                         }
                     }
                 }, UserHandle.USER_ALL);
+
         mPackageMonitor.register(mContext, mLocationHandler.getLooper(), true);
 
         // listen for user change
@@ -841,6 +863,7 @@
             for (String p : mUpdateRecords.keySet()) {
                 s.append(" ").append(mUpdateRecords.get(p).toString());
             }
+            s.append(" monitoring location: ").append(mOpMonitoring);
             s.append("]");
             return s.toString();
         }
@@ -913,7 +936,7 @@
                 }
             } else {
                 if (!allowMonitoring
-                        || mAppOps.checkOpNoThrow(op, mIdentity.mUid, mIdentity.mPackageName)
+                        || mAppOps.noteOpNoThrow(op, mIdentity.mUid, mIdentity.mPackageName)
                         != AppOpsManager.MODE_ALLOWED) {
                     mAppOps.finishOp(op, mIdentity.mUid, mIdentity.mPackageName);
                     return false;
@@ -1541,7 +1564,7 @@
     boolean checkLocationAccess(int pid, int uid, String packageName, int allowedResolutionLevel) {
         int op = resolutionLevelToOp(allowedResolutionLevel);
         if (op >= 0) {
-            if (mAppOps.checkOp(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) {
+            if (mAppOps.noteOp(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) {
                 return false;
             }
         }
@@ -1857,6 +1880,14 @@
                 Arrays.asList(setting.split(",")));
     }
 
+    private void updateLastLocationMaxAgeLocked() {
+        mLastLocationMaxAgeMs =
+                Settings.Global.getLong(
+                        mContext.getContentResolver(),
+                        Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS,
+                        DEFAULT_LAST_LOCATION_MAX_AGE_MS);
+    }
+
     private boolean isThrottlingExemptLocked(Identity identity) {
         if (identity.mUid == Process.SYSTEM_UID) {
             return true;
@@ -2262,6 +2293,17 @@
                 if (location == null) {
                     return null;
                 }
+
+                // Don't return stale location to apps with foreground-only location permission.
+                String op = getResolutionPermission(allowedResolutionLevel);
+                long locationAgeMs = SystemClock.elapsedRealtime() -
+                        location.getElapsedRealtimeNanos() / NANOS_PER_MILLI;
+                if ((locationAgeMs > mLastLocationMaxAgeMs)
+                        && (mAppOps.unsafeCheckOp(op, uid, packageName)
+                            == AppOpsManager.MODE_FOREGROUND)) {
+                    return null;
+                }
+
                 if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
                     Location noGPSLocation = location.getExtraLocation(
                             Location.EXTRA_NO_GPS_LOCATION);