content: /*
   * Copyright (C) 2007 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.server;
  
  import static android.content.pm.PackageManager.PERMISSION_GRANTED;
  import static android.location.LocationManager.FUSED_PROVIDER;
  import static android.location.LocationManager.GPS_PROVIDER;
  import static android.location.LocationManager.NETWORK_PROVIDER;
  import static android.location.LocationManager.PASSIVE_PROVIDER;
  import static android.location.LocationProvider.AVAILABLE;
  import static android.os.PowerManager.locationPowerSaveModeToString;
  import static android.provider.Settings.Global.LOCATION_DISABLE_STATUS_CALLBACKS;
  
  import static com.android.internal.util.Preconditions.checkNotNull;
  import static com.android.internal.util.Preconditions.checkState;
  
  import android.Manifest;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.app.ActivityManager;
  import android.app.AppOpsManager;
  import android.app.PendingIntent;
  import android.content.BroadcastReceiver;
  import android.content.Context;
  import android.content.Intent;
  import android.content.IntentFilter;
  import android.content.pm.ApplicationInfo;
  import android.content.pm.PackageInfo;
  import android.content.pm.PackageManager;
  import android.content.pm.PackageManager.NameNotFoundException;
  import android.content.pm.PackageManagerInternal;
  import android.content.pm.ResolveInfo;
  import android.content.pm.Signature;
  import android.content.res.Resources;
  import android.database.ContentObserver;
  import android.hardware.location.ActivityRecognitionHardware;
  import android.location.Address;
  import android.location.Criteria;
  import android.location.GeocoderParams;
  import android.location.Geofence;
  import android.location.GnssCapabilities;
  import android.location.GnssMeasurementCorrections;
  import android.location.IBatchedLocationCallback;
  import android.location.IGnssMeasurementsListener;
  import android.location.IGnssNavigationMessageListener;
  import android.location.IGnssStatusListener;
  import android.location.IGpsGeofenceHardware;
  import android.location.ILocationListener;
  import android.location.ILocationManager;
  import android.location.INetInitiatedListener;
  import android.location.Location;
  import android.location.LocationManager;
  import android.location.LocationRequest;
  import android.location.LocationTime;
  import android.os.Binder;
  import android.os.Bundle;
  import android.os.Handler;
  import android.os.IBinder;
  import android.os.IInterface;
  import android.os.PowerManager;
  import android.os.PowerManager.ServiceType;
  import android.os.PowerManagerInternal;
  import android.os.Process;
  import android.os.RemoteException;
  import android.os.SystemClock;
  import android.os.UserHandle;
  import android.os.UserManager;
  import android.os.WorkSource;
  import android.os.WorkSource.WorkChain;
  import android.provider.Settings;
  import android.stats.location.LocationStatsEnums;
  import android.text.TextUtils;
  import android.util.ArrayMap;
  import android.util.ArraySet;
  import android.util.EventLog;
  import android.util.Log;
  import android.util.Slog;
  import android.util.TimeUtils;
  
  import com.android.internal.annotations.GuardedBy;
  import com.android.internal.content.PackageMonitor;
  import com.android.internal.location.ProviderProperties;
  import com.android.internal.location.ProviderRequest;
  import com.android.internal.util.ArrayUtils;
  import com.android.internal.util.DumpUtils;
  import com.android.internal.util.Preconditions;
  import com.android.server.location.AbstractLocationProvider;
  import com.android.server.location.ActivityRecognitionProxy;
  import com.android.server.location.CallerIdentity;
  import com.android.server.location.GeocoderProxy;
  import com.android.server.location.GeofenceManager;
  import com.android.server.location.GeofenceProxy;
  import com.android.server.location.GnssBatchingProvider;
  import com.android.server.location.GnssCapabilitiesProvider;
  import com.android.server.location.GnssLocationProvider;
  import com.android.server.location.GnssMeasurementCorrectionsProvider;
  import com.android.server.location.GnssMeasurementsProvider;
  import com.android.server.location.GnssNavigationMessageProvider;
  import com.android.server.location.GnssStatusListenerHelper;
  import com.android.server.location.LocationBlacklist;
  import com.android.server.location.LocationFudger;
  import com.android.server.location.LocationProviderProxy;
  import com.android.server.location.LocationRequestStatistics;
  import com.android.server.location.LocationRequestStatistics.PackageProviderKey;
  import com.android.server.location.LocationRequestStatistics.PackageStatistics;
  import com.android.server.location.MockProvider;
  import com.android.server.location.PassiveProvider;
  import com.android.server.location.RemoteListenerHelper;
  
  import java.io.ByteArrayOutputStream;
  import java.io.FileDescriptor;
  import java.io.PrintStream;
  import java.io.PrintWriter;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Map.Entry;
  import java.util.NoSuchElementException;
  import java.util.function.Consumer;
  import java.util.function.Function;
  
  /**
   * The service class that manages LocationProviders and issues location
   * updates and alerts.
   */
  public class LocationManagerService extends ILocationManager.Stub {
      private static final String TAG = "LocationManagerService";
      public static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
  
      private static final String WAKELOCK_KEY = "*location*";
  
      // Location resolution level: no location data whatsoever
      private static final int RESOLUTION_LEVEL_NONE = 0;
      // Location resolution level: coarse location data only
      private static final int RESOLUTION_LEVEL_COARSE = 1;
      // Location resolution level: fine location data
      private static final int RESOLUTION_LEVEL_FINE = 2;
  
      private static final String ACCESS_LOCATION_EXTRA_COMMANDS =
              android.Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS;
  
      private static final String NETWORK_LOCATION_SERVICE_ACTION =
              "com.android.location.service.v3.NetworkLocationProvider";
      private static final String FUSED_LOCATION_SERVICE_ACTION =
              "com.android.location.service.FusedLocationProvider";
  
      private static final long NANOS_PER_MILLI = 1000000L;
  
      // 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;
  
      private static final int FOREGROUND_IMPORTANCE_CUTOFF
              = 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;
  
      // 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
      // time
      private static final int MAX_PROVIDER_SCHEDULING_JITTER_MS = 100;
  
      private static final LocationRequest DEFAULT_LOCATION_REQUEST = new LocationRequest();
  
      private final Object mLock = new Object();
      private final Context mContext;
      private final Handler mHandler;
  
      private AppOpsManager mAppOps;
      private PackageManager mPackageManager;
      private PowerManager mPowerManager;
      private ActivityManager mActivityManager;
      private UserManager mUserManager;
  
      private GeofenceManager mGeofenceManager;
      private LocationFudger mLocationFudger;
      private GeocoderProxy mGeocodeProvider;
      private GnssStatusListenerHelper mGnssStatusProvider;
      private INetInitiatedListener mNetInitiatedListener;
      private PassiveProvider mPassiveProvider;  // track passive provider for special cases
      private LocationBlacklist mBlacklist;
      private GnssMeasurementsProvider mGnssMeasurementsProvider;
      private GnssMeasurementCorrectionsProvider mGnssMeasurementCorrectionsProvider;
      private GnssNavigationMessageProvider mGnssNavigationMessageProvider;
      @GuardedBy("mLock")
      private String mExtraLocationControllerPackage;
      private boolean mExtraLocationControllerPackageEnabled;
      private IGpsGeofenceHardware mGpsGeofenceProxy;
  
      // list of currently active providers
      @GuardedBy("mLock")
      private final ArrayList<LocationProvider> mProviders = new ArrayList<>();
  
      // list of non-mock providers, so that when mock providers replace real providers, they can be
      // later re-replaced
      @GuardedBy("mLock")
      private final ArrayList<LocationProvider> mRealProviders = new ArrayList<>();
  
      @GuardedBy("mLock")
      private final HashMap<Object, Receiver> mReceivers = new HashMap<>();
      private final HashMap<String, ArrayList<UpdateRecord>> mRecordsByProvider =
              new HashMap<>();
  
      private final LocationRequestStatistics mRequestStatistics = new LocationRequestStatistics();
  
      // mapping from provider name to last known location
      @GuardedBy("mLock")
      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.
      @GuardedBy("mLock")
      private final HashMap<String, Location> mLastLocationCoarseInterval =
              new HashMap<>();
  
      private final ArraySet<String> mBackgroundThrottlePackageWhitelist = new ArraySet<>();
  
      private final ArraySet<String> mIgnoreSettingsPackageWhitelist = new ArraySet<>();
  
      @GuardedBy("mLock")
      private final ArrayMap<IBinder, LinkedListener<IGnssMeasurementsListener>>
              mGnssMeasurementsListeners = new ArrayMap<>();
      @GuardedBy("mLock")
      private final ArrayMap<IBinder, LinkedListener<IGnssNavigationMessageListener>>
              mGnssNavigationMessageListeners = new ArrayMap<>();
      @GuardedBy("mLock")
      private final ArrayMap<IBinder, LinkedListener<IGnssStatusListener>>
              mGnssStatusListeners = new ArrayMap<>();
  
      // 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 GnssLocationProvider.GnssSystemInfoProvider mGnssSystemInfoProvider;
      private GnssLocationProvider.GnssMetricsProvider mGnssMetricsProvider;
      private GnssCapabilitiesProvider mGnssCapabilitiesProvider;
  
      private GnssBatchingProvider mGnssBatchingProvider;
      @GuardedBy("mLock")
      private IBatchedLocationCallback mGnssBatchingCallback;
      @GuardedBy("mLock")
      private LinkedListener<IBatchedLocationCallback> mGnssBatchingDeathCallback;
      @GuardedBy("mLock")
      private boolean mGnssBatchingInProgress = false;
  
      @GuardedBy("mLock")
      @PowerManager.LocationPowerSaveMode
      private int mBatterySaverMode;
  
      @GuardedBy("mLock")
      private final LocationUsageLogger mLocationUsageLogger;
  
      public LocationManagerService(Context context) {
          super();
          mContext = context;
          mHandler = FgThread.getHandler();
          mLocationUsageLogger = new LocationUsageLogger();
  
          // Let the package manager query which are the default location
          // providers as they get certain permissions granted by default.
          PackageManagerInternal packageManagerInternal = LocalServices.getService(
                  PackageManagerInternal.class);
          packageManagerInternal.setLocationPackagesProvider(
                  userId -> mContext.getResources().getStringArray(
                          com.android.internal.R.array.config_locationProviderPackageNames));
          packageManagerInternal.setLocationExtraPackagesProvider(
                  userId -> mContext.getResources().getStringArray(
                        com.android.internal.R.array.config_locationExtraPackageNames));
  
          // most startup is deferred until systemRunning()
      }
  
      public void systemRunning() {
          synchronized (mLock) {
              initializeLocked();
          }
      }
  
      @GuardedBy("mLock")
      private void initializeLocked() {
          mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
          mPackageManager = mContext.getPackageManager();
          mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
          mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
          mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
  
          mLocationFudger = new LocationFudger(mContext, mHandler);
          mBlacklist = new LocationBlacklist(mContext, mHandler);
          mBlacklist.init();
          mGeofenceManager = new GeofenceManager(mContext, mBlacklist);
  
          // prepare providers
          initializeProvidersLocked();
  
          // add listeners
          mAppOps.startWatchingMode(
                  AppOpsManager.OP_COARSE_LOCATION,
                  null,
                  AppOpsManager.WATCH_FOREGROUND_CHANGES,
                  new AppOpsManager.OnOpChangedInternalListener() {
                      public void onOpChanged(int op, String packageName) {
                          // onOpChanged invoked on ui thread, move to our thread to reduce risk of
                          // blocking ui thread
                          mHandler.post(() -> {
                              synchronized (mLock) {
                                  onAppOpChangedLocked();
                              }
                          });
                      }
                  });
          mPackageManager.addOnPermissionsChangeListener(
                  uid -> {
                      // listener invoked on ui thread, move to our thread to reduce risk of blocking
                      // ui thread
                      mHandler.post(() -> {
                          synchronized (mLock) {
                              onPermissionsChangedLocked();
                          }
                      });
                  });
  
          mActivityManager.addOnUidImportanceListener(
                  (uid, importance) -> {
                      // listener invoked on ui thread, move to our thread to reduce risk of blocking
                      // ui thread
                      mHandler.post(() -> {
                          synchronized (mLock) {
                              onUidImportanceChangedLocked(uid, importance);
                          }
                      });
                  },
                  FOREGROUND_IMPORTANCE_CUTOFF);
          mContext.getContentResolver().registerContentObserver(
                  Settings.Secure.getUriFor(Settings.Secure.LOCATION_MODE), true,
                  new ContentObserver(mHandler) {
                      @Override
                      public void onChange(boolean selfChange) {
                          synchronized (mLock) {
                              onLocationModeChangedLocked(true);
                          }
                      }
                  }, UserHandle.USER_ALL);
          mContext.getContentResolver().registerContentObserver(
                  Settings.Secure.getUriFor(Settings.Secure.LOCATION_PROVIDERS_ALLOWED), true,
                  new ContentObserver(mHandler) {
                      @Override
                      public void onChange(boolean selfChange) {
                          synchronized (mLock) {
                              onProviderAllowedChangedLocked();
                          }
                      }
                  }, UserHandle.USER_ALL);
          mContext.getContentResolver().registerContentObserver(
                  Settings.Global.getUriFor(Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS),
                  true,
                  new ContentObserver(mHandler) {
                      @Override
                      public void onChange(boolean selfChange) {
                          synchronized (mLock) {
                              onBackgroundThrottleIntervalChangedLocked();
                          }
                      }
                  }, UserHandle.USER_ALL);
          mContext.getContentResolver().registerContentObserver(
                  Settings.Global.getUriFor(
                          Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST),
                  true,
                  new ContentObserver(mHandler) {
                      @Override
                      public void onChange(boolean selfChange) {
                          synchronized (mLock) {
                              onBackgroundThrottleWhitelistChangedLocked();
                          }
                      }
                  }, UserHandle.USER_ALL);
          mContext.getContentResolver().registerContentObserver(
                  Settings.Global.getUriFor(
                          Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST),
                  true,
                  new ContentObserver(mHandler) {
                      @Override
                      public void onChange(boolean selfChange) {
                          synchronized (mLock) {
                              onIgnoreSettingsWhitelistChangedLocked();
                          }
                      }
                  }, UserHandle.USER_ALL);
          PowerManagerInternal localPowerManager =
                  LocalServices.getService(PowerManagerInternal.class);
          localPowerManager.registerLowPowerModeObserver(ServiceType.LOCATION,
                  state -> {
                      // listener invoked on ui thread, move to our thread to reduce risk of blocking
                      // ui thread
                      mHandler.post(() -> {
                          synchronized (mLock) {
                              onBatterySaverModeChangedLocked(state.locationMode);
                          }
                      });
                  });
  
          new PackageMonitor() {
              @Override
              public void onPackageDisappeared(String packageName, int reason) {
                  synchronized (mLock) {
                      LocationManagerService.this.onPackageDisappearedLocked(packageName);
                  }
              }
          }.register(mContext, mHandler.getLooper(), true);
  
          IntentFilter intentFilter = new IntentFilter();
          intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
          intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
          intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
          intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
          intentFilter.addAction(Intent.ACTION_SCREEN_ON);
  
          mContext.registerReceiverAsUser(new BroadcastReceiver() {
              @Override
              public void onReceive(Context context, Intent intent) {
                  final String action = intent.getAction();
                  if (action == null) {
                      return;
                  }
                  synchronized (mLock) {
                      switch (action) {
                          case Intent.ACTION_USER_SWITCHED:
                              onUserChangedLocked(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
                              break;
                          case Intent.ACTION_MANAGED_PROFILE_ADDED:
                          case Intent.ACTION_MANAGED_PROFILE_REMOVED:
                              onUserProfilesChangedLocked();
                              break;
                          case Intent.ACTION_SCREEN_ON:
                          case Intent.ACTION_SCREEN_OFF:
                              onScreenStateChangedLocked();
                              break;
                      }
                  }
              }
          }, UserHandle.ALL, intentFilter, null, mHandler);
  
          // switching the user from null to system here performs the bulk of the initialization work.
          // the user being changed will cause a reload of all user specific settings, which causes
          // provider initialization, and propagates changes until a steady state is reached
          mCurrentUserId = UserHandle.USER_NULL;
          onUserChangedLocked(ActivityManager.getCurrentUser());
  
          // initialize in-memory settings values
          onBackgroundThrottleWhitelistChangedLocked();
          onIgnoreSettingsWhitelistChangedLocked();
          onBatterySaverModeChangedLocked(mPowerManager.getLocationPowerSaveMode());
      }
  
      @GuardedBy("mLock")
      private void onAppOpChangedLocked() {
          for (Receiver receiver : mReceivers.values()) {
              receiver.updateMonitoring(true);
          }
          for (LocationProvider p : mProviders) {
              applyRequirementsLocked(p);
          }
      }
  
      @GuardedBy("mLock")
      private void onPermissionsChangedLocked() {
          for (LocationProvider p : mProviders) {
              applyRequirementsLocked(p);
          }
      }
  
      @GuardedBy("mLock")
      private void onBatterySaverModeChangedLocked(int newLocationMode) {
          if (D) {
              Slog.d(TAG,
                      "Battery Saver location mode changed from "
                              + locationPowerSaveModeToString(mBatterySaverMode) + " to "
                              + locationPowerSaveModeToString(newLocationMode));
          }
  
          if (mBatterySaverMode == newLocationMode) {
              return;
          }
  
          mBatterySaverMode = newLocationMode;
          for (LocationProvider p : mProviders) {
              applyRequirementsLocked(p);
          }
      }
  
      @GuardedBy("mLock")
      private void onScreenStateChangedLocked() {
          if (mBatterySaverMode == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF) {
              for (LocationProvider p : mProviders) {
                  applyRequirementsLocked(p);
              }
          }
      }
  
      @GuardedBy("mLock")
      private void onLocationModeChangedLocked(boolean broadcast) {
          if (D) {
              Log.d(TAG, "location enabled is now " + isLocationEnabled());
          }
  
          for (LocationProvider p : mProviders) {
              p.onLocationModeChangedLocked();
          }
  
          if (broadcast) {
              // needs to be sent to everyone because we don't know which user may have changed
              // LOCATION_MODE state.
              mContext.sendBroadcastAsUser(
                      new Intent(LocationManager.MODE_CHANGED_ACTION),
                      UserHandle.ALL);
          }
      }
  
      @GuardedBy("mLock")
      private void onProviderAllowedChangedLocked() {
          for (LocationProvider p : mProviders) {
              p.onAllowedChangedLocked();
          }
      }
  
      @GuardedBy("mLock")
      private void onPackageDisappearedLocked(String packageName) {
          ArrayList<Receiver> deadReceivers = null;
  
          for (Receiver receiver : mReceivers.values()) {
              if (receiver.mCallerIdentity.mPackageName.equals(packageName)) {
                  if (deadReceivers == null) {
                      deadReceivers = new ArrayList<>();
                  }
                  deadReceivers.add(receiver);
              }
          }
  
          // perform removal outside of mReceivers loop
          if (deadReceivers != null) {
              for (Receiver receiver : deadReceivers) {
                  removeUpdatesLocked(receiver);
              }
          }
      }
  
      @GuardedBy("mLock")
      private void onUidImportanceChangedLocked(int uid, int importance) {
          boolean foreground = isImportanceForeground(importance);
          HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size());
          for (Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) {
              String provider = entry.getKey();
              for (UpdateRecord record : entry.getValue()) {
                  if (record.mReceiver.mCallerIdentity.mUid == uid
                          && record.mIsForegroundUid != foreground) {
                      if (D) {
                          Log.d(TAG, "request from uid " + uid + " is now "
                                  + foregroundAsString(foreground));
                      }
                      record.updateForeground(foreground);
  
                      if (!isThrottlingExemptLocked(record.mReceiver.mCallerIdentity)) {
                          affectedProviders.add(provider);
                      }
                  }
              }
          }
          for (String provider : affectedProviders) {
              applyRequirementsLocked(provider);
          }
  
          updateGnssDataProviderOnUidImportanceChangedLocked(mGnssMeasurementsListeners,
                  mGnssMeasurementsProvider, IGnssMeasurementsListener.Stub::asInterface,
                  uid, foreground);
  
          updateGnssDataProviderOnUidImportanceChangedLocked(mGnssNavigationMessageListeners,
                  mGnssNavigationMessageProvider, IGnssNavigationMessageListener.Stub::asInterface,
                  uid, foreground);
  
          updateGnssDataProviderOnUidImportanceChangedLocked(mGnssStatusListeners,
                  mGnssStatusProvider, IGnssStatusListener.Stub::asInterface, uid, foreground);
      }
  
      @GuardedBy("mLock")
      private <TListener extends IInterface> void updateGnssDataProviderOnUidImportanceChangedLocked(
              ArrayMap<IBinder, ? extends LinkedListenerBase> gnssDataListeners,
              RemoteListenerHelper<TListener> gnssDataProvider,
              Function<IBinder, TListener> mapBinderToListener, int uid, boolean foreground) {
          for (Entry<IBinder, ? extends LinkedListenerBase> entry : gnssDataListeners.entrySet()) {
              LinkedListenerBase linkedListener = entry.getValue();
              CallerIdentity callerIdentity = linkedListener.mCallerIdentity;
              if (callerIdentity.mUid != uid) {
                  continue;
              }
  
              if (D) {
                  Log.d(TAG, linkedListener.mListenerName + " from uid "
                          + uid + " is now " + foregroundAsString(foreground));
              }
  
              TListener listener = mapBinderToListener.apply(entry.getKey());
              if (foreground || isThrottlingExemptLocked(callerIdentity)) {
                  gnssDataProvider.addListener(listener, callerIdentity);
              } else {
                  gnssDataProvider.removeListener(listener);
              }
          }
      }
  
      private static String foregroundAsString(boolean foreground) {
          return foreground ? "foreground" : "background";
      }
  
      private static boolean isImportanceForeground(int importance) {
          return importance <= FOREGROUND_IMPORTANCE_CUTOFF;
      }
  
      @GuardedBy("mLock")
      private void onBackgroundThrottleIntervalChangedLocked() {
          for (LocationProvider provider : mProviders) {
              applyRequirementsLocked(provider);
          }
      }
  
      @GuardedBy("mLock")
      private void onBackgroundThrottleWhitelistChangedLocked() {
          mBackgroundThrottlePackageWhitelist.clear();
          mBackgroundThrottlePackageWhitelist.addAll(
                  SystemConfig.getInstance().getAllowUnthrottledLocation());
  
          String setting = Settings.Global.getString(
                  mContext.getContentResolver(),
                  Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST);
          if (!TextUtils.isEmpty(setting)) {
              mBackgroundThrottlePackageWhitelist.addAll(Arrays.asList(setting.split(",")));
          }
  
          for (LocationProvider p : mProviders) {
              applyRequirementsLocked(p);
          }
      }
  
      @GuardedBy("lock")
      private void onIgnoreSettingsWhitelistChangedLocked() {
          mIgnoreSettingsPackageWhitelist.clear();
          mIgnoreSettingsPackageWhitelist.addAll(
                  SystemConfig.getInstance().getAllowIgnoreLocationSettings());
  
          String setting = Settings.Global.getString(
                  mContext.getContentResolver(),
                  Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST);
          if (!TextUtils.isEmpty(setting)) {
              mIgnoreSettingsPackageWhitelist.addAll(Arrays.asList(setting.split(",")));
          }
  
          for (LocationProvider p : mProviders) {
              applyRequirementsLocked(p);
          }
      }
  
      @GuardedBy("mLock")
      private void onUserProfilesChangedLocked() {
          mCurrentUserProfiles = mUserManager.getProfileIdsWithDisabled(mCurrentUserId);
      }
  
      @GuardedBy("mLock")
      private boolean isCurrentProfileLocked(int userId) {
          return ArrayUtils.contains(mCurrentUserProfiles, userId);
      }
  
      @GuardedBy("mLock")
      private void ensureFallbackFusedProviderPresentLocked(String[] pkgs) {
          PackageManager pm = mContext.getPackageManager();
          String systemPackageName = mContext.getPackageName();
          ArrayList<HashSet<Signature>> sigSets = ServiceWatcher.getSignatureSets(mContext, pkgs);
  
          List<ResolveInfo> rInfos = pm.queryIntentServicesAsUser(
                  new Intent(FUSED_LOCATION_SERVICE_ACTION),
                  PackageManager.GET_META_DATA, mCurrentUserId);
          for (ResolveInfo rInfo : rInfos) {
              String packageName = rInfo.serviceInfo.packageName;
  
              // Check that the signature is in the list of supported sigs. If it's not in
              // this list the standard provider binding logic won't bind to it.
              try {
                  PackageInfo pInfo;
                  pInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
                  if (!ServiceWatcher.isSignatureMatch(pInfo.signatures, sigSets)) {
                      Log.w(TAG, packageName + " resolves service " + FUSED_LOCATION_SERVICE_ACTION +
                              ", but has wrong signature, ignoring");
                      continue;
                  }
              } catch (NameNotFoundException e) {
                  Log.e(TAG, "missing package: " + packageName);
                  continue;
              }
  
              // Get the version info
              if (rInfo.serviceInfo.metaData == null) {
                  Log.w(TAG, "Found fused provider without metadata: " + packageName);
                  continue;
              }
  
              int version = rInfo.serviceInfo.metaData.getInt(
                      ServiceWatcher.EXTRA_SERVICE_VERSION, -1);
              if (version == 0) {
                  // This should be the fallback fused location provider.
  
                  // Make sure it's in the system partition.
                  if ((rInfo.serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
                      if (D) Log.d(TAG, "Fallback candidate not in /system: " + packageName);
                      continue;
                  }
  
                  // Check that the fallback is signed the same as the OS
                  // 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);
                      }
                      continue;
                  }
  
                  // Found a valid fallback.
                  if (D) Log.d(TAG, "Found fallback provider: " + packageName);
                  return;
              } else {
                  if (D) Log.d(TAG, "Fallback candidate not version 0: " + packageName);
              }
          }
  
          throw new IllegalStateException("Unable to find a fused location provider that is in the "
                  + "system partition with version 0 and signed with the platform certificate. "
                  + "Such a package is needed to provide a default fused location provider in the "
                  + "event that no other fused location provider has been installed or is currently "
                  + "available. For example, coreOnly boot mode when decrypting the data "
                  + "partition. The fallback must also be marked coreApp=\"true\" in the manifest");
      }
  
      @GuardedBy("mLock")
      private void initializeProvidersLocked() {
          // create a passive location provider, which is always enabled
          LocationProvider passiveProviderManager = new LocationProvider(PASSIVE_PROVIDER);
          addProviderLocked(passiveProviderManager);
          mPassiveProvider = new PassiveProvider(mContext, passiveProviderManager);
          passiveProviderManager.attachLocked(mPassiveProvider);
  
          if (GnssLocationProvider.isSupported()) {
              // Create a gps location provider
              LocationProvider gnssProviderManager = new LocationProvider(GPS_PROVIDER, true);
              mRealProviders.add(gnssProviderManager);
              addProviderLocked(gnssProviderManager);
  
              GnssLocationProvider gnssProvider = new GnssLocationProvider(mContext,
                      gnssProviderManager,
                      mHandler.getLooper());
              gnssProviderManager.attachLocked(gnssProvider);
  
              mGnssSystemInfoProvider = gnssProvider.getGnssSystemInfoProvider();
              mGnssBatchingProvider = gnssProvider.getGnssBatchingProvider();
              mGnssMetricsProvider = gnssProvider.getGnssMetricsProvider();
              mGnssCapabilitiesProvider = gnssProvider.getGnssCapabilitiesProvider();
              mGnssStatusProvider = gnssProvider.getGnssStatusProvider();
              mNetInitiatedListener = gnssProvider.getNetInitiatedListener();
              mGnssMeasurementsProvider = gnssProvider.getGnssMeasurementsProvider();
              mGnssMeasurementCorrectionsProvider =
                      gnssProvider.getGnssMeasurementCorrectionsProvider();
              mGnssNavigationMessageProvider = gnssProvider.getGnssNavigationMessageProvider();
              mGpsGeofenceProxy = gnssProvider.getGpsGeofenceProxy();
          }
  
          /*
          Load package name(s) containing location provider support.
          These packages can contain services implementing location providers:
          Geocoder Provider, Network Location Provider, and
          Fused Location Provider. They will each be searched for
          service components implementing these providers.
          The location framework also has support for installation
          of new location providers at run-time. The new package does not
          have to be explicitly listed here, however it must have a signature
          that matches the signature of at least one package on this list.
          */
          Resources resources = mContext.getResources();
          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));
          }
  
          ensureFallbackFusedProviderPresentLocked(pkgs);
  
          // bind to network provider
          LocationProvider networkProviderManager = new LocationProvider(NETWORK_PROVIDER, true);
          LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind(
                  mContext,
                  networkProviderManager,
                  NETWORK_LOCATION_SERVICE_ACTION,
                  com.android.internal.R.bool.config_enableNetworkLocationOverlay,
                  com.android.internal.R.string.config_networkLocationProviderPackageName,
                  com.android.internal.R.array.config_locationProviderPackageNames);
          if (networkProvider != null) {
              mRealProviders.add(networkProviderManager);
              addProviderLocked(networkProviderManager);
              networkProviderManager.attachLocked(networkProvider);
          } else {
              Slog.w(TAG, "no network location provider found");
          }
  
          // bind to fused provider
          LocationProvider fusedProviderManager = new LocationProvider(FUSED_PROVIDER);
          LocationProviderProxy fusedProvider = LocationProviderProxy.createAndBind(
                  mContext,
                  fusedProviderManager,
                  FUSED_LOCATION_SERVICE_ACTION,
                  com.android.internal.R.bool.config_enableFusedLocationOverlay,
                  com.android.internal.R.string.config_fusedLocationProviderPackageName,
                  com.android.internal.R.array.config_locationProviderPackageNames);
          if (fusedProvider != null) {
              mRealProviders.add(fusedProviderManager);
              addProviderLocked(fusedProviderManager);
              fusedProviderManager.attachLocked(fusedProvider);
          } else {
              Slog.e(TAG, "no fused location provider found",
                      new IllegalStateException("Location service needs a fused location provider"));
          }
  
          // bind to geocoder provider
          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);
          if (mGeocodeProvider == null) {
              Slog.e(TAG, "no geocoder provider found");
          }
  
          // bind to geofence provider
          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,
                  mGpsGeofenceProxy,
                  null);
          if (provider == null) {
              Slog.d(TAG, "Unable to bind FLP Geofence proxy.");
          }
  
          // bind to hardware activity recognition
          boolean activityRecognitionHardwareIsSupported = ActivityRecognitionHardware.isSupported();
          ActivityRecognitionHardware activityRecognitionHardware = null;
          if (activityRecognitionHardwareIsSupported) {
              activityRecognitionHardware = ActivityRecognitionHardware.getInstance(mContext);
          } else {
              Slog.d(TAG, "Hardware Activity-Recognition not supported.");
          }
          ActivityRecognitionProxy proxy = ActivityRecognitionProxy.createAndBind(
                  mContext,
                  activityRecognitionHardwareIsSupported,
                  activityRecognitionHardware,
                  com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay,
                  com.android.internal.R.string.config_activityRecognitionHardwarePackageName,
                  com.android.internal.R.array.config_locationProviderPackageNames);
          if (proxy == null) {
              Slog.d(TAG, "Unable to bind ActivityRecognitionProxy.");
          }
  
          String[] testProviderStrings = resources.getStringArray(
                  com.android.internal.R.array.config_testLocationProviders);
          for (String testProviderString : testProviderStrings) {
              String[] fragments = testProviderString.split(",");
              String name = fragments[0].trim();
              ProviderProperties properties = new ProviderProperties(
                      Boolean.parseBoolean(fragments[1]) /* requiresNetwork */,
                      Boolean.parseBoolean(fragments[2]) /* requiresSatellite */,
                      Boolean.parseBoolean(fragments[3]) /* requiresCell */,
                      Boolean.parseBoolean(fragments[4]) /* hasMonetaryCost */,
                      Boolean.parseBoolean(fragments[5]) /* supportsAltitude */,
                      Boolean.parseBoolean(fragments[6]) /* supportsSpeed */,
                      Boolean.parseBoolean(fragments[7]) /* supportsBearing */,
                      Integer.parseInt(fragments[8]) /* powerRequirement */,
                      Integer.parseInt(fragments[9]) /* accuracy */);
              LocationProvider testProviderManager = new LocationProvider(name);
              addProviderLocked(testProviderManager);
              new MockProvider(mContext, testProviderManager, properties);
          }
      }
  
      @GuardedBy("mLock")
      private void onUserChangedLocked(int userId) {
          if (mCurrentUserId == userId) {
              return;
          }
  
          if (D) {
              Log.d(TAG, "foreground user is changing to " + userId);
          }
  
          // let providers know the current user is on the way out before changing the user
          for (LocationProvider p : mProviders) {
              p.onUserChangingLocked();
          }
  
          mCurrentUserId = userId;
          onUserProfilesChangedLocked();
  
          mBlacklist.switchUser(userId);
  
          // if the user changes, per-user settings may also have changed
          onLocationModeChangedLocked(false);
          onProviderAllowedChangedLocked();
  
          // always force useability to be rechecked, even if no per-user settings have changed
          for (LocationProvider p : mProviders) {
              p.onUseableChangedLocked(false);
          }
      }
  
      private class LocationProvider implements AbstractLocationProvider.LocationProviderManager {
  
          private final String mName;
  
          // whether this provider should respect LOCATION_PROVIDERS_ALLOWED (ie gps and network)
          private final boolean mIsManagedBySettings;
  
          // remember to clear binder identity before invoking any provider operation
          @GuardedBy("mLock")
          @Nullable protected AbstractLocationProvider mProvider;
  
          @GuardedBy("mLock")
          private boolean mUseable;  // combined state
          @GuardedBy("mLock")
          private boolean mAllowed;  // state of LOCATION_PROVIDERS_ALLOWED
          @GuardedBy("mLock")
          private boolean mEnabled;  // state of provider
  
          @GuardedBy("mLock")
          @Nullable private ProviderProperties mProperties;
  
          private LocationProvider(String name) {
              this(name, false);
          }
  
          private LocationProvider(String name, boolean isManagedBySettings) {
              mName = name;
              mIsManagedBySettings = isManagedBySettings;
  
              mProvider = null;
              mUseable = false;
              mAllowed = !mIsManagedBySettings;
              mEnabled = false;
              mProperties = null;
  
              if (mIsManagedBySettings) {
                  // since we assume providers are disabled by default
                  Settings.Secure.putStringForUser(
                          mContext.getContentResolver(),
                          Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
                          "-" + mName,
                          mCurrentUserId);
              }
          }
  
          @GuardedBy("mLock")
          public void attachLocked(AbstractLocationProvider provider) {
              checkNotNull(provider);
              checkState(mProvider == null);
  
              if (D) {
                  Log.d(TAG, mName + " provider attached");
              }
  
              mProvider = provider;
              onUseableChangedLocked(false);
          }
  
          public String getName() {
              return mName;
          }
  
          @GuardedBy("mLock")
          public List<String> getPackagesLocked() {
              if (mProvider == null) {
                  return Collections.emptyList();
              } else {
                  // safe to not clear binder context since this doesn't call into the real provider
                  return mProvider.getProviderPackages();
              }
          }
  
          public boolean isMock() {
              return false;
          }
  
          @GuardedBy("mLock")
          public boolean isPassiveLocked() {
              return mProvider == mPassiveProvider;
          }
  
          @GuardedBy("mLock")
          @Nullable
          public ProviderProperties getPropertiesLocked() {
              return mProperties;
          }
  
          @GuardedBy("mLock")
          public void setRequestLocked(ProviderRequest request, WorkSource workSource) {
              if (mProvider != null) {
                  long identity = Binder.clearCallingIdentity();
                  try {
                      mProvider.setRequest(request, workSource);
                  } finally {
                      Binder.restoreCallingIdentity(identity);
                  }
              }
          }
  
          @GuardedBy("mLock")
          public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
              pw.print("  " + mName + " provider");
              if (isMock()) {
                  pw.print(" [mock]");
              }
              pw.println(":");
  
              pw.println("    useable=" + mUseable);
              if (!mUseable) {
                  pw.println("    attached=" + (mProvider != null));
                  if (mIsManagedBySettings) {
                      pw.println("    allowed=" + mAllowed);
                  }
                  pw.println("    enabled=" + mEnabled);
              }
  
              pw.println("    properties=" + mProperties);
  
              if (mProvider != null) {
                  long identity = Binder.clearCallingIdentity();
                  try {
                      mProvider.dump(fd, pw, args);
                  } finally {
                      Binder.restoreCallingIdentity(identity);
                  }
              }
          }
  
          @GuardedBy("mLock")
          public long getStatusUpdateTimeLocked() {
              if (mProvider != null) {
                  long identity = Binder.clearCallingIdentity();
                  try {
                      return mProvider.getStatusUpdateTime();
                  } finally {
                      Binder.restoreCallingIdentity(identity);
                  }
              } else {
                  return 0;
              }
          }
  
          @GuardedBy("mLock")
          public int getStatusLocked(Bundle extras) {
              if (mProvider != null) {
                  long identity = Binder.clearCallingIdentity();
                  try {
                      return mProvider.getStatus(extras);
                  } finally {
                      Binder.restoreCallingIdentity(identity);
                  }
              } else {
                  return AVAILABLE;
              }
          }
  
          @GuardedBy("mLock")
          public void sendExtraCommandLocked(String command, Bundle extras) {
              if (mProvider != null) {
                  long identity = Binder.clearCallingIdentity();
                  try {
                      mProvider.sendExtraCommand(command, extras);
                  } finally {
                      Binder.restoreCallingIdentity(identity);
                  }
              }
          }
  
          // called from any thread
          @Override
          public void onReportLocation(Location location) {
              // no security check necessary because this is coming from an internal-only interface
              // move calls coming from below LMS onto a different thread to avoid deadlock
              mHandler.post(() -> {
                  synchronized (mLock) {
                      handleLocationChangedLocked(location, this);
                  }
              });
          }
  
          // called from any thread
          @Override
          public void onReportLocation(List<Location> locations) {
              // move calls coming from below LMS onto a different thread to avoid deadlock
              mHandler.post(() -> {
                  synchronized (mLock) {
                      LocationProvider gpsProvider = getLocationProviderLocked(GPS_PROVIDER);
                      if (gpsProvider == null || !gpsProvider.isUseableLocked()) {
                          Slog.w(TAG, "reportLocationBatch() called without user permission");
                          return;
                      }
  
                      if (mGnssBatchingCallback == null) {
                          Slog.e(TAG, "reportLocationBatch() called without active Callback");
                          return;
                      }
  
                      try {
                          mGnssBatchingCallback.onLocationBatch(locations);
                      } catch (RemoteException e) {
                          Slog.e(TAG, "mGnssBatchingCallback.onLocationBatch failed", e);
                      }
                  }
              });
          }
  
          // called from any thread
          @Override
          public void onSetEnabled(boolean enabled) {
              // move calls coming from below LMS onto a different thread to avoid deadlock
              mHandler.post(() -> {
                  synchronized (mLock) {
                      if (enabled == mEnabled) {
                          return;
                      }
  
                      if (D) {
                          Log.d(TAG, mName + " provider enabled is now " + mEnabled);
                      }
  
                      mEnabled = enabled;
                      onUseableChangedLocked(false);
                  }
              });
          }
  
          @Override
          public void onSetProperties(ProviderProperties properties) {
              // because this does not invoke any other methods which might result in calling back
              // into the location provider, it is safe to run this on the calling thread. it is also
              // currently necessary to run this on the calling thread to ensure that property changes
              // are publicly visibly immediately, ie for mock providers which are created.
              synchronized (mLock) {
                  mProperties = properties;
              }
          }
  
          @GuardedBy("mLock")
          public void onLocationModeChangedLocked() {
              onUseableChangedLocked(false);
          }
  
          @GuardedBy("mLock")
          public void onAllowedChangedLocked() {
              if (mIsManagedBySettings) {
                  String allowedProviders = Settings.Secure.getStringForUser(
                          mContext.getContentResolver(),
                          Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
                          mCurrentUserId);
                  boolean allowed = TextUtils.delimitedStringContains(allowedProviders, ',', mName);
  
                  if (allowed == mAllowed) {
                      return;
                  }
  
                  if (D) {
                      Log.d(TAG, mName + " provider allowed is now " + mAllowed);
                  }
  
                  mAllowed = allowed;
                  onUseableChangedLocked(true);
              }
          }
  
          @GuardedBy("mLock")
          public boolean isUseableLocked() {
              return isUseableForUserLocked(mCurrentUserId);
          }
  
          @GuardedBy("mLock")
          public boolean isUseableForUserLocked(int userId) {
              return isCurrentProfileLocked(userId) && mUseable;
          }
  
          @GuardedBy("mLock")
          private boolean isUseableIgnoringAllowedLocked() {
              return mProvider != null && mProviders.contains(this) && isLocationEnabled()
                      && mEnabled;
          }
  
          @GuardedBy("mLock")
          public void onUseableChangedLocked(boolean isAllowedChanged) {
              // if any property that contributes to "useability" here changes state, it MUST result
              // in a direct or indrect call to onUseableChangedLocked. this allows the provider to
              // guarantee that it will always eventually reach the correct state.
              boolean useableIgnoringAllowed = isUseableIgnoringAllowedLocked();
              boolean useable = useableIgnoringAllowed && mAllowed;
  
              // update deprecated provider allowed settings for backwards compatibility, and do this
              // even if there is no change in overall useability state. this may result in trying to
              // overwrite the same value, but Settings handles deduping this.
              if (mIsManagedBySettings) {
                  // a "-" change derived from the allowed setting should not be overwritten, but a
                  // "+" change should be corrected if necessary
                  if (useableIgnoringAllowed && !isAllowedChanged) {
                      Settings.Secure.putStringForUser(
                              mContext.getContentResolver(),
                              Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
                              "+" + mName,
                              mCurrentUserId);
                  } else if (!useableIgnoringAllowed) {
                      Settings.Secure.putStringForUser(
                              mContext.getContentResolver(),
                              Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
                              "-" + mName,
                              mCurrentUserId);
                  }
  
                  // needs to be sent to all users because whether or not a provider is enabled for
                  // a given user is complicated... we broadcast to everyone and let them figure it
                  // out via isProviderEnabled()
                  Intent intent = new Intent(LocationManager.PROVIDERS_CHANGED_ACTION);
                  intent.putExtra(LocationManager.EXTRA_PROVIDER_NAME, mName);
                  mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
              }
  
              if (useable == mUseable) {
                  return;
              }
              mUseable = useable;
  
              if (D) {
                  Log.d(TAG, mName + " provider useable is now " + mUseable);
              }
  
              if (!mUseable) {
                  // If any provider has been disabled, clear all last locations for all
                  // providers. This is to be on the safe side in case a provider has location
                  // derived from this disabled provider.
                  mLastLocation.clear();
                  mLastLocationCoarseInterval.clear();
              }
  
              updateProviderUseableLocked(this);
          }
  
          @GuardedBy("mLock")
          public void onUserChangingLocked() {
              // when the user is about to change, we set this provider to un-useable, and notify all
              // of the current user clients. when the user is finished changing, useability will be
              // updated back via onLocationModeChanged() and onAllowedChanged().
              mUseable = false;
              updateProviderUseableLocked(this);
          }
      }
  
      private class MockLocationProvider extends LocationProvider {
  
          private ProviderRequest mCurrentRequest;
  
          private MockLocationProvider(String name) {
              super(name);
          }
  
          @Override
          public void attachLocked(AbstractLocationProvider provider) {
              checkState(provider instanceof MockProvider);
              super.attachLocked(provider);
          }
  
          public boolean isMock() {
              return true;
          }
  
          @GuardedBy("mLock")
          public void setEnabledLocked(boolean enabled) {
              if (mProvider != null) {
                  long identity = Binder.clearCallingIdentity();
                  try {
                      ((MockProvider) mProvider).setEnabled(enabled);
                  } finally {
                      Binder.restoreCallingIdentity(identity);
                  }
              }
          }
  
          @GuardedBy("mLock")
          public void setLocationLocked(Location location) {
              if (mProvider != null) {
                  long identity = Binder.clearCallingIdentity();
                  try {
                      ((MockProvider) mProvider).setLocation(location);
                  } finally {
                      Binder.restoreCallingIdentity(identity);
                  }
              }
          }
  
          @Override
          @GuardedBy("mLock")
          public void setRequestLocked(ProviderRequest request, WorkSource workSource) {
              super.setRequestLocked(request, workSource);
              mCurrentRequest = request;
          }
  
          @GuardedBy("mLock")
          public void setStatusLocked(int status, Bundle extras, long updateTime) {
              if (mProvider != null) {
                  long identity = Binder.clearCallingIdentity();
                  try {
                      ((MockProvider) mProvider).setStatus(status, extras, updateTime);
                  } finally {
                      Binder.restoreCallingIdentity(identity);
                  }
              }
          }
      }
  
      /**
       * A wrapper class holding either an ILocationListener or a PendingIntent to receive
       * location updates.
       */
      private final class Receiver extends LinkedListenerBase implements PendingIntent.OnFinished {
          private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000;
          private final int mAllowedResolutionLevel;  // resolution level allowed to receiver
  
          private final ILocationListener mListener;
          final PendingIntent mPendingIntent;
          final WorkSource mWorkSource; // WorkSource for battery blame, or null to assign to caller.
          private final boolean mHideFromAppOps; // True if AppOps should not monitor this receiver.
          private final Object mKey;
  
          final HashMap<String, UpdateRecord> mUpdateRecords = new HashMap<>();
  
          // True if app ops has started monitoring this receiver for locations.
          private boolean mOpMonitoring;
          // True if app ops has started monitoring this receiver for high power (gps) locations.
          private boolean mOpHighPowerMonitoring;
          private int mPendingBroadcasts;
          PowerManager.WakeLock mWakeLock;
  
          private Receiver(ILocationListener listener, PendingIntent intent, int pid, int uid,
                  String packageName, WorkSource workSource, boolean hideFromAppOps) {
              super(new CallerIdentity(uid, pid, packageName), "LocationListener");
              mListener = listener;
              mPendingIntent = intent;
              if (listener != null) {
                  mKey = listener.asBinder();
              } else {
                  mKey = intent;
              }
              mAllowedResolutionLevel = getAllowedResolutionLevel(pid, uid);
              if (workSource != null && workSource.isEmpty()) {
                  workSource = null;
              }
              mWorkSource = workSource;
              mHideFromAppOps = hideFromAppOps;
  
              updateMonitoring(true);
  
              // construct/configure wakelock
              mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
              if (workSource == null) {
                  workSource = new WorkSource(mCallerIdentity.mUid, mCallerIdentity.mPackageName);
              }
              mWakeLock.setWorkSource(workSource);
  
              // For a non-reference counted wakelock, each acquire will reset the timeout, and we
              // only need to release it once.
              mWakeLock.setReferenceCounted(false);
          }
  
          @Override
          public boolean equals(Object otherObj) {
              return (otherObj instanceof Receiver) && mKey.equals(((Receiver) otherObj).mKey);
          }
  
          @Override
          public int hashCode() {
              return mKey.hashCode();
          }
  
          @Override
          public String toString() {
              StringBuilder s = new StringBuilder();
              s.append("Reciever[");
              s.append(Integer.toHexString(System.identityHashCode(this)));
              if (mListener != null) {
                  s.append(" listener");
              } else {
                  s.append(" intent");
              }
              for (String p : mUpdateRecords.keySet()) {
                  s.append(" ").append(mUpdateRecords.get(p).toString());
              }
              s.append(" monitoring location: ").append(mOpMonitoring);
              s.append("]");
              return s.toString();
          }
  
          /**
           * Update AppOp monitoring for this receiver.
           *
           * @param allow If true receiver is currently active, if false it's been removed.
           */
          public void updateMonitoring(boolean allow) {
              if (mHideFromAppOps) {
                  return;
              }
  
              boolean requestingLocation = false;
              boolean requestingHighPowerLocation = false;
              if (allow) {
                  // See if receiver has any enabled update records.  Also note if any update records
                  // are high power (has a high power provider with an interval under a threshold).
                  for (UpdateRecord updateRecord : mUpdateRecords.values()) {
                      LocationProvider provider = getLocationProviderLocked(updateRecord.mProvider);
                      if (provider == null) {
                          continue;
                      }
                      if (!provider.isUseableLocked() && !isSettingsExemptLocked(updateRecord)) {
                          continue;
                      }
  
                      requestingLocation = true;
                      ProviderProperties properties = provider.getPropertiesLocked();
                      if (properties != null
                              && properties.mPowerRequirement == Criteria.POWER_HIGH
                              && updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) {
                          requestingHighPowerLocation = true;
                          break;
                      }
                  }
              }
  
              // First update monitoring of any location request (including high power).
              mOpMonitoring = updateMonitoring(
                      requestingLocation,
                      mOpMonitoring,
                      AppOpsManager.OP_MONITOR_LOCATION);
  
              // Now update monitoring of high power requests only.
              boolean wasHighPowerMonitoring = mOpHighPowerMonitoring;
              mOpHighPowerMonitoring = updateMonitoring(
                      requestingHighPowerLocation,
                      mOpHighPowerMonitoring,
                      AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION);
              if (mOpHighPowerMonitoring != wasHighPowerMonitoring) {
                  // Send an intent to notify that a high power request has been added/removed.
                  Intent intent = new Intent(LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION);
                  mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
              }
          }
  
          /**
           * Update AppOps monitoring for a single location request and op type.
           *
           * @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.
           * @return True if monitoring is on for this request/op after updating.
           */
          private boolean updateMonitoring(boolean allowMonitoring, boolean currentlyMonitoring,
                  int op) {
              if (!currentlyMonitoring) {
                  if (allowMonitoring) {
                      return mAppOps.startOpNoThrow(op, mCallerIdentity.mUid,
                              mCallerIdentity.mPackageName) == AppOpsManager.MODE_ALLOWED;
                  }
              } else {
                  if (!allowMonitoring
                          || mAppOps.checkOpNoThrow(op, mCallerIdentity.mUid,
                          mCallerIdentity.mPackageName) != AppOpsManager.MODE_ALLOWED) {
                      mAppOps.finishOp(op, mCallerIdentity.mUid, mCallerIdentity.mPackageName);
                      return false;
                  }
              }
  
              return currentlyMonitoring;
          }
  
          public boolean isListener() {
              return mListener != null;
          }
  
          public boolean isPendingIntent() {
              return mPendingIntent != null;
          }
  
          public ILocationListener getListener() {
              if (mListener != null) {
                  return mListener;
              }
              throw new IllegalStateException("Request for non-existent listener");
          }
  
          public boolean callStatusChangedLocked(String provider, int status, Bundle extras) {
              if (mListener != null) {
                  try {
                      mListener.onStatusChanged(provider, status, extras);
                      // call this after broadcasting so we do not increment
                      // if we throw an exception.
                      incrementPendingBroadcastsLocked();
                  } catch (RemoteException e) {
                      return false;
                  }
              } else {
                  Intent statusChanged = new Intent();
                  statusChanged.putExtras(new Bundle(extras));
                  statusChanged.putExtra(LocationManager.KEY_STATUS_CHANGED, status);
                  try {
                      mPendingIntent.send(mContext, 0, statusChanged, this, mHandler,
                              getResolutionPermission(mAllowedResolutionLevel),
                              PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
                      // call this after broadcasting so we do not increment
                      // if we throw an exception.
                      incrementPendingBroadcastsLocked();
                  } catch (PendingIntent.CanceledException e) {
                      return false;
                  }
              }
              return true;
          }
  
          public boolean callLocationChangedLocked(Location location) {
              if (mListener != null) {
                  try {
                      mListener.onLocationChanged(new Location(location));
                      // call this after broadcasting so we do not increment
                      // if we throw an exception.
                      incrementPendingBroadcastsLocked();
                  } catch (RemoteException e) {
                      return false;
                  }
              } else {
                  Intent locationChanged = new Intent();
                  locationChanged.putExtra(LocationManager.KEY_LOCATION_CHANGED,
                          new Location(location));
                  try {
                      mPendingIntent.send(mContext, 0, locationChanged, this, mHandler,
                              getResolutionPermission(mAllowedResolutionLevel),
                              PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
                      // call this after broadcasting so we do not increment
                      // if we throw an exception.
                      incrementPendingBroadcastsLocked();
                  } catch (PendingIntent.CanceledException e) {
                      return false;
                  }
              }
              return true;
          }
  
          private boolean callProviderEnabledLocked(String provider, boolean enabled) {
              // First update AppOp monitoring.
              // An app may get/lose location access as providers are enabled/disabled.
              updateMonitoring(true);
  
              if (mListener != null) {
                  try {
                      if (enabled) {
                          mListener.onProviderEnabled(provider);
                      } else {
                          mListener.onProviderDisabled(provider);
                      }
                      // call this after broadcasting so we do not increment
                      // if we throw an exception.
                      incrementPendingBroadcastsLocked();
                  } catch (RemoteException e) {
                      return false;
                  }
              } else {
                  Intent providerIntent = new Intent();
                  providerIntent.putExtra(LocationManager.KEY_PROVIDER_ENABLED, enabled);
                  try {
                      mPendingIntent.send(mContext, 0, providerIntent, this, mHandler,
                              getResolutionPermission(mAllowedResolutionLevel),
                              PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
                      // call this after broadcasting so we do not increment
                      // if we throw an exception.
                      incrementPendingBroadcastsLocked();
                  } catch (PendingIntent.CanceledException e) {
                      return false;
                  }
              }
              return true;
          }
  
          @Override
          public void binderDied() {
              if (D) Log.d(TAG, "Remote " + mListenerName + " died.");
  
              synchronized (mLock) {
                  removeUpdatesLocked(this);
                  clearPendingBroadcastsLocked();
              }
          }
  
          @Override
          public void onSendFinished(PendingIntent pendingIntent, Intent intent,
                  int resultCode, String resultData, Bundle resultExtras) {
              synchronized (mLock) {
                  decrementPendingBroadcastsLocked();
              }
          }
  
          // this must be called while synchronized by caller in a synchronized block
          // containing the sending of the broadcaset
          private void incrementPendingBroadcastsLocked() {
              mPendingBroadcasts++;
              // so wakelock calls will succeed
              long identity = Binder.clearCallingIdentity();
              try {
                  mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          private void decrementPendingBroadcastsLocked() {
              if (--mPendingBroadcasts == 0) {
                  // so wakelock calls will succeed
                  long identity = Binder.clearCallingIdentity();
                  try {
                      if (mWakeLock.isHeld()) {
                          mWakeLock.release();
                      }
                  } finally {
                      Binder.restoreCallingIdentity(identity);
                  }
              }
          }
  
          public void clearPendingBroadcastsLocked() {
              if (mPendingBroadcasts > 0) {
                  mPendingBroadcasts = 0;
                  // so wakelock calls will succeed
                  long identity = Binder.clearCallingIdentity();
                  try {
                      if (mWakeLock.isHeld()) {
                          mWakeLock.release();
                      }
                  } finally {
                      Binder.restoreCallingIdentity(identity);
                  }
              }
          }
      }
  
      @Override
      public void locationCallbackFinished(ILocationListener listener) {
          //Do not use getReceiverLocked here as that will add the ILocationListener to
          //the receiver list if it is not found.  If it is not found then the
          //LocationListener was removed when it had a pending broadcast and should
          //not be added back.
          synchronized (mLock) {
              Receiver receiver = mReceivers.get(listener.asBinder());
              if (receiver != null) {
                  receiver.decrementPendingBroadcastsLocked();
              }
          }
      }
  
      @Override
      public int getGnssYearOfHardware() {
          if (mGnssSystemInfoProvider != null) {
              return mGnssSystemInfoProvider.getGnssYearOfHardware();
          } else {
              return 0;
          }
      }
  
      @Override
      @Nullable
      public String getGnssHardwareModelName() {
          if (mGnssSystemInfoProvider != null) {
              return mGnssSystemInfoProvider.getGnssHardwareModelName();
          } else {
              return null;
          }
      }
  
      private boolean hasGnssPermissions(String packageName) {
          synchronized (mLock) {
              int allowedResolutionLevel = getCallerAllowedResolutionLevel();
              checkResolutionLevelIsSufficientForProviderUseLocked(
                      allowedResolutionLevel,
                      GPS_PROVIDER);
  
              int pid = Binder.getCallingPid();
              int uid = Binder.getCallingUid();
              long identity = Binder.clearCallingIdentity();
              try {
                  return checkLocationAccess(pid, uid, packageName, allowedResolutionLevel);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
      }
  
      @Override
      public int getGnssBatchSize(String packageName) {
          mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
                  "Location Hardware permission not granted to access hardware batching");
  
          if (hasGnssPermissions(packageName) && mGnssBatchingProvider != null) {
              return mGnssBatchingProvider.getBatchSize();
          } else {
              return 0;
          }
      }
  
      @Override
      public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName) {
          mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
                  "Location Hardware permission not granted to access hardware batching");
  
          if (!hasGnssPermissions(packageName) || mGnssBatchingProvider == null) {
              return false;
          }
  
          CallerIdentity callerIdentity = new CallerIdentity(Binder.getCallingUid(),
                  Binder.getCallingPid(), packageName);
          synchronized (mLock) {
              mGnssBatchingCallback = callback;
              mGnssBatchingDeathCallback =  new LinkedListener<>(callback,
                      "BatchedLocationCallback", callerIdentity,
                      (IBatchedLocationCallback listener) -> {
                          stopGnssBatch();
                          removeGnssBatchingCallback();
                      });
              if (!linkToListenerDeathNotificationLocked(callback.asBinder(),
                      mGnssBatchingDeathCallback)) {
                  return false;
              }
              return true;
          }
      }
  
      @Override
      public void removeGnssBatchingCallback() {
          synchronized (mLock) {
              unlinkFromListenerDeathNotificationLocked(mGnssBatchingCallback.asBinder(),
                      mGnssBatchingDeathCallback);
              mGnssBatchingCallback = null;
              mGnssBatchingDeathCallback = null;
          }
      }
  
      @Override
      public boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName) {
          mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
                  "Location Hardware permission not granted to access hardware batching");
  
          if (!hasGnssPermissions(packageName) || mGnssBatchingProvider == null) {
              return false;
          }
  
          synchronized (mLock) {
              if (mGnssBatchingInProgress) {
                  // Current design does not expect multiple starts to be called repeatedly
                  Log.e(TAG, "startGnssBatch unexpectedly called w/o stopping prior batch");
                  // Try to clean up anyway, and continue
                  stopGnssBatch();
              }
  
              mGnssBatchingInProgress = true;
              return mGnssBatchingProvider.start(periodNanos, wakeOnFifoFull);
          }
      }
  
      @Override
      public void flushGnssBatch(String packageName) {
          mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
                  "Location Hardware permission not granted to access hardware batching");
  
          if (!hasGnssPermissions(packageName)) {
              Log.e(TAG, "flushGnssBatch called without GNSS permissions");
              return;
          }
  
          synchronized (mLock) {
              if (!mGnssBatchingInProgress) {
                  Log.w(TAG, "flushGnssBatch called with no batch in progress");
              }
  
              if (mGnssBatchingProvider != null) {
                  mGnssBatchingProvider.flush();
              }
          }
      }
  
      @Override
      public boolean stopGnssBatch() {
          mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
                  "Location Hardware permission not granted to access hardware batching");
  
          synchronized (mLock) {
              if (mGnssBatchingProvider != null) {
                  mGnssBatchingInProgress = false;
                  return mGnssBatchingProvider.stop();
              } else {
                  return false;
              }
          }
      }
  
      @GuardedBy("mLock")
      private void addProviderLocked(LocationProvider provider) {
          Preconditions.checkState(getLocationProviderLocked(provider.getName()) == null);
  
          mProviders.add(provider);
  
          provider.onAllowedChangedLocked();  // allowed state may change while provider was inactive
          provider.onUseableChangedLocked(false);
      }
  
      @GuardedBy("mLock")
      private void removeProviderLocked(LocationProvider provider) {
          if (mProviders.remove(provider)) {
              provider.onUseableChangedLocked(false);
          }
      }
  
      @GuardedBy("mLock")
      @Nullable
      private LocationProvider getLocationProviderLocked(String providerName) {
          for (LocationProvider provider : mProviders) {
              if (providerName.equals(provider.getName())) {
                  return provider;
              }
          }
  
          return null;
      }
  
      private String getResolutionPermission(int resolutionLevel) {
          switch (resolutionLevel) {
              case RESOLUTION_LEVEL_FINE:
                  return android.Manifest.permission.ACCESS_FINE_LOCATION;
              case RESOLUTION_LEVEL_COARSE:
                  return android.Manifest.permission.ACCESS_COARSE_LOCATION;
              default:
                  return null;
          }
      }
  
      private int getAllowedResolutionLevel(int pid, int uid) {
          if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
                  pid, uid) == PERMISSION_GRANTED) {
              return RESOLUTION_LEVEL_FINE;
          } else if (mContext.checkPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION,
                  pid, uid) == PERMISSION_GRANTED) {
              return RESOLUTION_LEVEL_COARSE;
          } else {
              return RESOLUTION_LEVEL_NONE;
          }
      }
  
      private int getCallerAllowedResolutionLevel() {
          return getAllowedResolutionLevel(Binder.getCallingPid(), Binder.getCallingUid());
      }
  
      private void checkResolutionLevelIsSufficientForGeofenceUse(int allowedResolutionLevel) {
          if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
              throw new SecurityException("Geofence usage requires ACCESS_FINE_LOCATION permission");
          }
      }
  
      @GuardedBy("mLock")
      private int getMinimumResolutionLevelForProviderUseLocked(String provider) {
          if (GPS_PROVIDER.equals(provider) || PASSIVE_PROVIDER.equals(provider)) {
              // gps and passive providers require FINE permission
              return RESOLUTION_LEVEL_FINE;
          } else if (NETWORK_PROVIDER.equals(provider) || FUSED_PROVIDER.equals(provider)) {
              // network and fused providers are ok with COARSE or FINE
              return RESOLUTION_LEVEL_COARSE;
          } else {
              for (LocationProvider lp : mProviders) {
                  if (!lp.getName().equals(provider)) {
                      continue;
                  }
  
                  ProviderProperties properties = lp.getPropertiesLocked();
                  if (properties != null) {
                      if (properties.mRequiresSatellite) {
                          // provider requiring satellites require FINE permission
                          return RESOLUTION_LEVEL_FINE;
                      } else if (properties.mRequiresNetwork || properties.mRequiresCell) {
                          // provider requiring network and or cell require COARSE or FINE
                          return RESOLUTION_LEVEL_COARSE;
                      }
                  }
              }
          }
  
          return RESOLUTION_LEVEL_FINE; // if in doubt, require FINE
      }
  
      @GuardedBy("mLock")
      private void checkResolutionLevelIsSufficientForProviderUseLocked(int allowedResolutionLevel,
              String providerName) {
          int requiredResolutionLevel = getMinimumResolutionLevelForProviderUseLocked(providerName);
          if (allowedResolutionLevel < requiredResolutionLevel) {
              switch (requiredResolutionLevel) {
                  case RESOLUTION_LEVEL_FINE:
                      throw new SecurityException("\"" + providerName + "\" location provider " +
                              "requires ACCESS_FINE_LOCATION permission.");
                  case RESOLUTION_LEVEL_COARSE:
                      throw new SecurityException("\"" + providerName + "\" location provider " +
                              "requires ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission.");
                  default:
                      throw new SecurityException("Insufficient permission for \"" + providerName +
                              "\" location provider.");
              }
          }
      }
  
      public static int resolutionLevelToOp(int allowedResolutionLevel) {
          if (allowedResolutionLevel != RESOLUTION_LEVEL_NONE) {
              if (allowedResolutionLevel == RESOLUTION_LEVEL_COARSE) {
                  return AppOpsManager.OP_COARSE_LOCATION;
              } else {
                  return AppOpsManager.OP_FINE_LOCATION;
              }
          }
          return -1;
      }
  
      private static String resolutionLevelToOpStr(int allowedResolutionLevel) {
          switch (allowedResolutionLevel) {
              case RESOLUTION_LEVEL_COARSE:
                  return AppOpsManager.OPSTR_COARSE_LOCATION;
              case RESOLUTION_LEVEL_FINE:
                  return AppOpsManager.OPSTR_FINE_LOCATION;
              case RESOLUTION_LEVEL_NONE:
                  // The client is not allowed to get any location, so both FINE and COARSE ops will
                  // be denied. Pick the most restrictive one to be safe.
                  return AppOpsManager.OPSTR_FINE_LOCATION;
              default:
                  // Use the most restrictive ops if not sure.
                  return AppOpsManager.OPSTR_FINE_LOCATION;
          }
      }
  
      private boolean reportLocationAccessNoThrow(
              int pid, int uid, String packageName, int allowedResolutionLevel) {
          int op = resolutionLevelToOp(allowedResolutionLevel);
          if (op >= 0) {
              if (mAppOps.noteOpNoThrow(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) {
                  return false;
              }
          }
  
          return getAllowedResolutionLevel(pid, uid) >= allowedResolutionLevel;
      }
  
      private 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) {
                  return false;
              }
          }
  
          return getAllowedResolutionLevel(pid, uid) >= allowedResolutionLevel;
      }
  
      /**
       * Returns all providers by name, including passive and the ones that are not permitted to
       * be accessed by the calling activity or are currently disabled, but excluding fused.
       */
      @Override
      public List<String> getAllProviders() {
          synchronized (mLock) {
              ArrayList<String> providers = new ArrayList<>(mProviders.size());
              for (LocationProvider provider : mProviders) {
                  String name = provider.getName();
                  if (FUSED_PROVIDER.equals(name)) {
                      continue;
                  }
                  providers.add(name);
              }
              return providers;
          }
      }
  
      /**
       * Return all providers by name, that match criteria and are optionally
       * enabled.
       * Can return passive provider, but never returns fused provider.
       */
      @Override
      public List<String> getProviders(Criteria criteria, boolean enabledOnly) {
          int allowedResolutionLevel = getCallerAllowedResolutionLevel();
          synchronized (mLock) {
              ArrayList<String> providers = new ArrayList<>(mProviders.size());
              for (LocationProvider provider : mProviders) {
                  String name = provider.getName();
                  if (FUSED_PROVIDER.equals(name)) {
                      continue;
                  }
                  if (allowedResolutionLevel < getMinimumResolutionLevelForProviderUseLocked(name)) {
                      continue;
                  }
                  if (enabledOnly && !provider.isUseableLocked()) {
                      continue;
                  }
                  if (criteria != null
                          && !android.location.LocationProvider.propertiesMeetCriteria(
                          name, provider.getPropertiesLocked(), criteria)) {
                      continue;
                  }
                  providers.add(name);
              }
              return providers;
          }
      }
  
      /**
       * Return the name of the best provider given a Criteria object.
       * This method has been deprecated from the public API,
       * and the whole LocationProvider (including #meetsCriteria)
       * has been deprecated as well. So this method now uses
       * some simplified logic.
       */
      @Override
      public String getBestProvider(Criteria criteria, boolean enabledOnly) {
          List<String> providers = getProviders(criteria, enabledOnly);
          if (providers.isEmpty()) {
              providers = getProviders(null, enabledOnly);
          }
  
          if (!providers.isEmpty()) {
              if (providers.contains(GPS_PROVIDER)) {
                  return GPS_PROVIDER;
              } else if (providers.contains(NETWORK_PROVIDER)) {
                  return NETWORK_PROVIDER;
              } else {
                  return providers.get(0);
              }
          }
  
          return null;
      }
  
      @GuardedBy("mLock")
      private void updateProviderUseableLocked(LocationProvider provider) {
          boolean useable = provider.isUseableLocked();
  
          ArrayList<Receiver> deadReceivers = null;
  
          ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
          if (records != null) {
              for (UpdateRecord record : records) {
                  if (!isCurrentProfileLocked(
                          UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
                      continue;
                  }
  
                  // requests that ignore location settings will never provide notifications
                  if (isSettingsExemptLocked(record)) {
                      continue;
                  }
  
                  // Sends a notification message to the receiver
                  if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) {
                      if (deadReceivers == null) {
                          deadReceivers = new ArrayList<>();
                      }
                      deadReceivers.add(record.mReceiver);
                  }
              }
          }
  
          if (deadReceivers != null) {
              for (int i = deadReceivers.size() - 1; i >= 0; i--) {
                  removeUpdatesLocked(deadReceivers.get(i));
              }
          }
  
          applyRequirementsLocked(provider);
      }
  
      @GuardedBy("mLock")
      private void applyRequirementsLocked(String providerName) {
          LocationProvider provider = getLocationProviderLocked(providerName);
          if (provider != null) {
              applyRequirementsLocked(provider);
          }
      }
  
      @GuardedBy("mLock")
      private void applyRequirementsLocked(LocationProvider provider) {
          ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
          WorkSource worksource = new WorkSource();
          ProviderRequest providerRequest = new ProviderRequest();
  
          // if provider is not active, it should not respond to requests
  
          if (mProviders.contains(provider) && records != null && !records.isEmpty()) {
              long backgroundThrottleInterval;
  
              long identity = Binder.clearCallingIdentity();
              try {
                  backgroundThrottleInterval = Settings.Global.getLong(
                          mContext.getContentResolver(),
                          Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
                          DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
  
              final boolean isForegroundOnlyMode =
                      mBatterySaverMode == PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
              final boolean shouldThrottleRequests =
                      mBatterySaverMode
                              == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF
                              && !mPowerManager.isInteractive();
              // initialize the low power mode to true and set to false if any of the records requires
              providerRequest.lowPowerMode = true;
              for (UpdateRecord record : records) {
                  if (!isCurrentProfileLocked(
                          UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
                      continue;
                  }
                  if (!checkLocationAccess(
                          record.mReceiver.mCallerIdentity.mPid,
                          record.mReceiver.mCallerIdentity.mUid,
                          record.mReceiver.mCallerIdentity.mPackageName,
                          record.mReceiver.mAllowedResolutionLevel)) {
                      continue;
                  }
                  final boolean isBatterySaverDisablingLocation = shouldThrottleRequests
                          || (isForegroundOnlyMode && !record.mIsForegroundUid);
                  if (!provider.isUseableLocked() || isBatterySaverDisablingLocation) {
                      if (isSettingsExemptLocked(record)) {
                          providerRequest.locationSettingsIgnored = true;
                          providerRequest.lowPowerMode = false;
                      } else {
                          continue;
                      }
                  }
  
                  LocationRequest locationRequest = record.mRealRequest;
                  long interval = locationRequest.getInterval();
  
  
                  // if we're forcing location, don't apply any throttling
                  if (!providerRequest.locationSettingsIgnored && !isThrottlingExemptLocked(
                          record.mReceiver.mCallerIdentity)) {
                      if (!record.mIsForegroundUid) {
                          interval = Math.max(interval, backgroundThrottleInterval);
                      }
                      if (interval != locationRequest.getInterval()) {
                          locationRequest = new LocationRequest(locationRequest);
                          locationRequest.setInterval(interval);
                      }
                  }
  
                  record.mRequest = locationRequest;
                  providerRequest.locationRequests.add(locationRequest);
                  if (!locationRequest.isLowPowerMode()) {
                      providerRequest.lowPowerMode = false;
                  }
                  if (interval < providerRequest.interval) {
                      providerRequest.reportLocation = true;
                      providerRequest.interval = interval;
                  }
              }
  
              if (providerRequest.reportLocation) {
                  // calculate who to blame for power
                  // This is somewhat arbitrary. We pick a threshold interval
                  // that is slightly higher that the minimum interval, and
                  // spread the blame across all applications with a request
                  // under that threshold.
                  long thresholdInterval = (providerRequest.interval + 1000) * 3 / 2;
                  for (UpdateRecord record : records) {
                      if (isCurrentProfileLocked(
                              UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
                          LocationRequest locationRequest = record.mRequest;
  
                          // Don't assign battery blame for update records whose
                          // client has no permission to receive location data.
                          if (!providerRequest.locationRequests.contains(locationRequest)) {
                              continue;
                          }
  
                          if (locationRequest.getInterval() <= thresholdInterval) {
                              if (record.mReceiver.mWorkSource != null
                                      && isValidWorkSource(record.mReceiver.mWorkSource)) {
                                  worksource.add(record.mReceiver.mWorkSource);
                              } else {
                                  // Assign blame to caller if there's no WorkSource associated with
                                  // the request or if it's invalid.
                                  worksource.add(
                                          record.mReceiver.mCallerIdentity.mUid,
                                          record.mReceiver.mCallerIdentity.mPackageName);
                              }
                          }
                      }
                  }
              }
          }
  
          provider.setRequestLocked(providerRequest, worksource);
      }
  
      /**
       * Whether a given {@code WorkSource} associated with a Location request is valid.
       */
      private static boolean isValidWorkSource(WorkSource workSource) {
          if (workSource.size() > 0) {
              // If the WorkSource has one or more non-chained UIDs, make sure they're accompanied
              // by tags.
              return workSource.getName(0) != null;
          } else {
              // For now, make sure callers have supplied an attribution tag for use with
              // AppOpsManager. This might be relaxed in the future.
              final ArrayList<WorkChain> workChains = workSource.getWorkChains();
              return workChains != null && !workChains.isEmpty() &&
                      workChains.get(0).getAttributionTag() != null;
          }
      }
  
      @Override
      public String[] getBackgroundThrottlingWhitelist() {
          synchronized (mLock) {
              return mBackgroundThrottlePackageWhitelist.toArray(new String[0]);
          }
      }
  
      @Override
      public String[] getIgnoreSettingsWhitelist() {
          synchronized (mLock) {
              return mIgnoreSettingsPackageWhitelist.toArray(new String[0]);
          }
      }
  
      @GuardedBy("mLock")
      private boolean isThrottlingExemptLocked(CallerIdentity callerIdentity) {
          if (callerIdentity.mUid == Process.SYSTEM_UID) {
              return true;
          }
  
          if (mBackgroundThrottlePackageWhitelist.contains(callerIdentity.mPackageName)) {
              return true;
          }
  
          return isProviderPackage(callerIdentity.mPackageName);
  
      }
  
      @GuardedBy("mLock")
      private boolean isSettingsExemptLocked(UpdateRecord record) {
          if (!record.mRealRequest.isLocationSettingsIgnored()) {
              return false;
          }
  
          if (mIgnoreSettingsPackageWhitelist.contains(
                  record.mReceiver.mCallerIdentity.mPackageName)) {
              return true;
          }
  
          return isProviderPackage(record.mReceiver.mCallerIdentity.mPackageName);
  
      }
  
      private class UpdateRecord {
          final String mProvider;
          private final LocationRequest mRealRequest;  // original request from client
          LocationRequest mRequest;  // possibly throttled version of the request
          private final Receiver mReceiver;
          private boolean mIsForegroundUid;
          private Location mLastFixBroadcast;
          private long mLastStatusBroadcast;
          private Throwable mStackTrace;  // for debugging only
  
          /**
           * Note: must be constructed with lock held.
           */
          private UpdateRecord(String provider, LocationRequest request, Receiver receiver) {
              mProvider = provider;
              mRealRequest = request;
              mRequest = request;
              mReceiver = receiver;
              mIsForegroundUid = isImportanceForeground(
                      mActivityManager.getPackageImportance(mReceiver.mCallerIdentity.mPackageName));
  
              if (D && receiver.mCallerIdentity.mPid == Process.myPid()) {
                  mStackTrace = new Throwable();
              }
  
              ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
              if (records == null) {
                  records = new ArrayList<>();
                  mRecordsByProvider.put(provider, records);
              }
              if (!records.contains(this)) {
                  records.add(this);
              }
  
              // Update statistics for historical location requests by package/provider
              mRequestStatistics.startRequesting(
                      mReceiver.mCallerIdentity.mPackageName, provider, request.getInterval(),
                      mIsForegroundUid);
          }
  
          /**
           * Method to be called when record changes foreground/background
           */
          private void updateForeground(boolean isForeground) {
              mIsForegroundUid = isForeground;
              mRequestStatistics.updateForeground(
                      mReceiver.mCallerIdentity.mPackageName, mProvider, isForeground);
          }
  
          /**
           * Method to be called when a record will no longer be used.
           */
          private void disposeLocked(boolean removeReceiver) {
              String packageName = mReceiver.mCallerIdentity.mPackageName;
              mRequestStatistics.stopRequesting(packageName, mProvider);
  
              mLocationUsageLogger.logLocationApiUsage(
                      LocationStatsEnums.USAGE_ENDED,
                      LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
                      packageName,
                      mRealRequest,
                      mReceiver.isListener(),
                      mReceiver.isPendingIntent(),
                      /* geofence= */ null,
                      mActivityManager.getPackageImportance(packageName));
  
              // remove from mRecordsByProvider
              ArrayList<UpdateRecord> globalRecords = mRecordsByProvider.get(this.mProvider);
              if (globalRecords != null) {
                  globalRecords.remove(this);
              }
  
              if (!removeReceiver) return;  // the caller will handle the rest
  
              // remove from Receiver#mUpdateRecords
              HashMap<String, UpdateRecord> receiverRecords = mReceiver.mUpdateRecords;
              receiverRecords.remove(this.mProvider);
  
              // and also remove the Receiver if it has no more update records
              if (receiverRecords.size() == 0) {
                  removeUpdatesLocked(mReceiver);
              }
          }
  
          @Override
          public String toString() {
              StringBuilder b = new StringBuilder("UpdateRecord[");
              b.append(mProvider).append(" ");
              b.append(mReceiver.mCallerIdentity.mPackageName);
              b.append("(").append(mReceiver.mCallerIdentity.mUid);
              if (mIsForegroundUid) {
                  b.append(" foreground");
              } else {
                  b.append(" background");
              }
              b.append(") ");
              b.append(mRealRequest).append(" ").append(mReceiver.mWorkSource);
  
              if (mStackTrace != null) {
                  ByteArrayOutputStream tmp = new ByteArrayOutputStream();
                  mStackTrace.printStackTrace(new PrintStream(tmp));
                  b.append("\n\n").append(tmp.toString()).append("\n");
              }
  
              b.append("]");
              return b.toString();
          }
      }
  
      @GuardedBy("mLock")
      private Receiver getReceiverLocked(ILocationListener listener, int pid, int uid,
              String packageName, WorkSource workSource, boolean hideFromAppOps) {
          IBinder binder = listener.asBinder();
          Receiver receiver = mReceivers.get(binder);
          if (receiver == null) {
              receiver = new Receiver(listener, null, pid, uid, packageName, workSource,
                      hideFromAppOps);
              if (!linkToListenerDeathNotificationLocked(receiver.getListener().asBinder(),
                      receiver)) {
                  return null;
              }
              mReceivers.put(binder, receiver);
          }
          return receiver;
      }
  
      @GuardedBy("mLock")
      private Receiver getReceiverLocked(PendingIntent intent, int pid, int uid, String packageName,
              WorkSource workSource, boolean hideFromAppOps) {
          Receiver receiver = mReceivers.get(intent);
          if (receiver == null) {
              receiver = new Receiver(null, intent, pid, uid, packageName, workSource,
                      hideFromAppOps);
              mReceivers.put(intent, receiver);
          }
          return receiver;
      }
  
      /**
       * Creates a LocationRequest based upon the supplied LocationRequest that to meets resolution
       * and consistency requirements.
       *
       * @param request the LocationRequest from which to create a sanitized version
       * @return a version of request that meets the given resolution and consistency requirements
       * @hide
       */
      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:
                      sanitizedRequest.setQuality(LocationRequest.ACCURACY_BLOCK);
                      break;
                  case LocationRequest.POWER_HIGH:
                      sanitizedRequest.setQuality(LocationRequest.POWER_LOW);
                      break;
              }
              // throttle
              if (sanitizedRequest.getInterval() < LocationFudger.FASTEST_INTERVAL_MS) {
                  sanitizedRequest.setInterval(LocationFudger.FASTEST_INTERVAL_MS);
              }
              if (sanitizedRequest.getFastestInterval() < LocationFudger.FASTEST_INTERVAL_MS) {
                  sanitizedRequest.setFastestInterval(LocationFudger.FASTEST_INTERVAL_MS);
              }
          }
          // make getFastestInterval() the minimum of interval and fastest interval
          if (sanitizedRequest.getFastestInterval() > sanitizedRequest.getInterval()) {
              sanitizedRequest.setFastestInterval(request.getInterval());
          }
          return sanitizedRequest;
      }
  
      private void checkPackageName(String packageName) {
          if (packageName == null) {
              throw new SecurityException("invalid package name: " + null);
          }
          int uid = Binder.getCallingUid();
          String[] packages = mPackageManager.getPackagesForUid(uid);
          if (packages == null) {
              throw new SecurityException("invalid UID " + uid);
          }
          for (String pkg : packages) {
              if (packageName.equals(pkg)) return;
          }
          throw new SecurityException("invalid package name: " + packageName);
      }
  
      @Override
      public void requestLocationUpdates(LocationRequest request, ILocationListener listener,
              PendingIntent intent, String packageName) {
          synchronized (mLock) {
              if (request == null) request = DEFAULT_LOCATION_REQUEST;
              checkPackageName(packageName);
              int allowedResolutionLevel = getCallerAllowedResolutionLevel();
              checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel,
                      request.getProvider());
              WorkSource workSource = request.getWorkSource();
              if (workSource != null && !workSource.isEmpty()) {
                  mContext.enforceCallingOrSelfPermission(
                          Manifest.permission.UPDATE_DEVICE_STATS, null);
              }
              boolean hideFromAppOps = request.getHideFromAppOps();
              if (hideFromAppOps) {
                  mContext.enforceCallingOrSelfPermission(
                          Manifest.permission.UPDATE_APP_OPS_STATS, null);
              }
              if (request.isLocationSettingsIgnored()) {
                  mContext.enforceCallingOrSelfPermission(
                          Manifest.permission.WRITE_SECURE_SETTINGS, null);
              }
              boolean callerHasLocationHardwarePermission =
                      mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
                              == PERMISSION_GRANTED;
              LocationRequest sanitizedRequest = createSanitizedRequest(request,
                      allowedResolutionLevel,
                      callerHasLocationHardwarePermission);
  
              final int pid = Binder.getCallingPid();
              final int uid = Binder.getCallingUid();
  
              long identity = Binder.clearCallingIdentity();
              try {
  
                  // We don't check for MODE_IGNORED here; we will do that when we go to deliver
                  // a location.
                  checkLocationAccess(pid, uid, packageName, allowedResolutionLevel);
  
                  if (intent == null && listener == null) {
                      throw new IllegalArgumentException("need either listener or intent");
                  } else if (intent != null && listener != null) {
                      throw new IllegalArgumentException(
                              "cannot register both listener and intent");
                  }
  
                  mLocationUsageLogger.logLocationApiUsage(
                          LocationStatsEnums.USAGE_STARTED,
                          LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
                          packageName, request, listener != null, intent != null,
                          /* geofence= */ null,
                          mActivityManager.getPackageImportance(packageName));
  
                  Receiver receiver;
                  if (intent != null) {
                      receiver = getReceiverLocked(intent, pid, uid, packageName, workSource,
                              hideFromAppOps);
                  } else {
                      receiver = getReceiverLocked(listener, pid, uid, packageName, workSource,
                              hideFromAppOps);
                  }
                  requestLocationUpdatesLocked(sanitizedRequest, receiver, uid, packageName);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
      }
  
      @GuardedBy("mLock")
      private void requestLocationUpdatesLocked(LocationRequest request, Receiver receiver,
              int uid, String packageName) {
          // Figure out the provider. Either its explicitly request (legacy use cases), or
          // use the fused provider
          if (request == null) request = DEFAULT_LOCATION_REQUEST;
          String name = request.getProvider();
          if (name == null) {
              throw new IllegalArgumentException("provider name must not be null");
          }
  
          LocationProvider provider = getLocationProviderLocked(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")
                      + (isThrottlingExemptLocked(receiver.mCallerIdentity)
                      ? " [whitelisted]" : "") + ")");
          }
  
          UpdateRecord oldRecord = receiver.mUpdateRecords.put(name, record);
          if (oldRecord != null) {
              oldRecord.disposeLocked(false);
          }
  
          if (!provider.isUseableLocked() && !isSettingsExemptLocked(record)) {
              // Notify the listener that updates are currently disabled - but only if the request
              // does not ignore location settings
              receiver.callProviderEnabledLocked(name, false);
          }
  
          applyRequirementsLocked(name);
  
          // Update the monitoring here just in case multiple location requests were added to the
          // same receiver (this request may be high power and the initial might not have been).
          receiver.updateMonitoring(true);
      }
  
      @Override
      public void removeUpdates(ILocationListener listener, PendingIntent intent,
              String packageName) {
          checkPackageName(packageName);
  
          int pid = Binder.getCallingPid();
          int uid = Binder.getCallingUid();
  
          if (intent == null && listener == null) {
              throw new IllegalArgumentException("need either listener or intent");
          } else if (intent != null && listener != null) {
              throw new IllegalArgumentException("cannot register both listener and intent");
          }
  
          synchronized (mLock) {
              Receiver receiver;
              if (intent != null) {
                  receiver = getReceiverLocked(intent, pid, uid, packageName, null, false);
              } else {
                  receiver = getReceiverLocked(listener, pid, uid, packageName, null, false);
              }
  
              long identity = Binder.clearCallingIdentity();
              try {
                  removeUpdatesLocked(receiver);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
      }
  
      @GuardedBy("mLock")
      private void removeUpdatesLocked(Receiver receiver) {
          if (D) Log.i(TAG, "remove " + Integer.toHexString(System.identityHashCode(receiver)));
  
          if (mReceivers.remove(receiver.mKey) != null && receiver.isListener()) {
              unlinkFromListenerDeathNotificationLocked(receiver.getListener().asBinder(),
                      receiver);
              receiver.clearPendingBroadcastsLocked();
          }
  
          receiver.updateMonitoring(false);
  
          // Record which providers were associated with this listener
          HashSet<String> providers = new HashSet<>();
          HashMap<String, UpdateRecord> oldRecords = receiver.mUpdateRecords;
          if (oldRecords != null) {
              // Call dispose() on the obsolete update records.
              for (UpdateRecord record : oldRecords.values()) {
                  // Update statistics for historical location requests by package/provider
                  record.disposeLocked(false);
              }
              // Accumulate providers
              providers.addAll(oldRecords.keySet());
          }
  
          // update provider
          for (String provider : providers) {
              applyRequirementsLocked(provider);
          }
      }
  
      @Override
      public Location getLastLocation(LocationRequest r, String packageName) {
          synchronized (mLock) {
              LocationRequest request = r != null ? r : DEFAULT_LOCATION_REQUEST;
              int allowedResolutionLevel = getCallerAllowedResolutionLevel();
              checkPackageName(packageName);
              checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel,
                      request.getProvider());
              // no need to sanitize this request, as only the provider name is used
  
              final int pid = Binder.getCallingPid();
              final int uid = Binder.getCallingUid();
              final long identity = Binder.clearCallingIdentity();
              try {
                  if (mBlacklist.isBlacklisted(packageName)) {
                      if (D) {
                          Log.d(TAG, "not returning last loc for blacklisted app: "
                                  + packageName);
                      }
                      return null;
                  }
  
  
                  // Figure out the provider. Either its explicitly request (deprecated API's),
                  // or use the fused provider
                  String name = request.getProvider();
                  if (name == null) name = LocationManager.FUSED_PROVIDER;
                  LocationProvider provider = getLocationProviderLocked(name);
                  if (provider == null) return null;
  
                  // only the current user or location providers may get location this way
                  if (!isCurrentProfileLocked(UserHandle.getUserId(uid)) && !isProviderPackage(
                          packageName)) {
                      return null;
                  }
  
                  if (!provider.isUseableLocked()) {
                      return null;
                  }
  
                  Location location;
                  if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
                      // Make sure that an app with coarse permissions can't get frequent location
                      // updates by calling LocationManager.getLastKnownLocation repeatedly.
                      location = mLastLocationCoarseInterval.get(name);
                  } else {
                      location = mLastLocation.get(name);
                  }
                  if (location == null) {
                      return null;
                  }
  
                  // Don't return stale location to apps with foreground-only location permission.
                  String op = resolutionLevelToOpStr(allowedResolutionLevel);
                  long locationAgeMs = SystemClock.elapsedRealtime()
                          - location.getElapsedRealtimeNanos() / NANOS_PER_MILLI;
                  if ((locationAgeMs > Settings.Global.getLong(
                          mContext.getContentResolver(),
                          Settings.Global.LOCATION_LAST_LOCATION_MAX_AGE_MILLIS,
                          DEFAULT_LAST_LOCATION_MAX_AGE_MS))
                          && (mAppOps.unsafeCheckOp(op, uid, packageName)
                          == AppOpsManager.MODE_FOREGROUND)) {
                      return null;
                  }
  
                  Location lastLocation = null;
                  if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
                      Location noGPSLocation = location.getExtraLocation(
                              Location.EXTRA_NO_GPS_LOCATION);
                      if (noGPSLocation != null) {
                          lastLocation = new Location(mLocationFudger.getOrCreate(noGPSLocation));
                      }
                  } else {
                      lastLocation = new Location(location);
                  }
                  // Don't report location access if there is no last location to deliver.
                  if (lastLocation != null) {
                      if (!reportLocationAccessNoThrow(
                              pid, uid, packageName, allowedResolutionLevel)) {
                          if (D) {
                              Log.d(TAG, "not returning last loc for no op app: " + packageName);
                          }
                          lastLocation =  null;
                      }
                  }
                  return lastLocation;
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
      }
  
      @Override
      public LocationTime getGnssTimeMillis() {
          synchronized (mLock) {
              Location location = mLastLocation.get(LocationManager.GPS_PROVIDER);
              if (location == null) {
                  return null;
              }
              long currentNanos = SystemClock.elapsedRealtimeNanos();
              long deltaMs = (currentNanos - location.getElapsedRealtimeNanos()) / 1000000L;
              return new LocationTime(location.getTime() + deltaMs, currentNanos);
          }
      }
  
      @Override
      public boolean injectLocation(Location location) {
          mContext.enforceCallingPermission(android.Manifest.permission.LOCATION_HARDWARE,
                  "Location Hardware permission not granted to inject location");
          mContext.enforceCallingPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
                  "Access Fine Location permission not granted to inject Location");
  
          if (location == null) {
              if (D) {
                  Log.d(TAG, "injectLocation(): called with null location");
              }
              return false;
          }
  
          synchronized (mLock) {
              LocationProvider provider = getLocationProviderLocked(location.getProvider());
              if (provider == null || !provider.isUseableLocked()) {
                  return false;
              }
  
              // NOTE: If last location is already available, location is not injected.  If
              // provider's normal source (like a GPS chipset) have already provided an output
              // there is no need to inject this location.
              if (mLastLocation.get(provider.getName()) != null) {
                  return false;
              }
  
              updateLastLocationLocked(location, provider.getName());
              return true;
          }
      }
  
      @Override
      public void requestGeofence(LocationRequest request, Geofence geofence, PendingIntent intent,
              String packageName) {
          if (request == null) request = DEFAULT_LOCATION_REQUEST;
          int allowedResolutionLevel = getCallerAllowedResolutionLevel();
          checkResolutionLevelIsSufficientForGeofenceUse(allowedResolutionLevel);
          if (intent == null) {
              throw new IllegalArgumentException("invalid pending intent: " + null);
          }
          checkPackageName(packageName);
          synchronized (mLock) {
              checkResolutionLevelIsSufficientForProviderUseLocked(allowedResolutionLevel,
                      request.getProvider());
          }
          // Require that caller can manage given document
          boolean callerHasLocationHardwarePermission =
                  mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
                          == PERMISSION_GRANTED;
          LocationRequest sanitizedRequest = createSanitizedRequest(request,
                  allowedResolutionLevel,
                  callerHasLocationHardwarePermission);
  
          if (D) {
              Log.d(TAG, "requestGeofence: " + sanitizedRequest + " " + geofence + " " + intent);
          }
  
          // geo-fence manager uses the public location API, need to clear identity
          int uid = Binder.getCallingUid();
          if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM) {
              // temporary measure until geofences work for secondary users
              Log.w(TAG, "proximity alerts are currently available only to the primary user");
              return;
          }
          long identity = Binder.clearCallingIdentity();
          try {
              synchronized (mLock) {
                  mLocationUsageLogger.logLocationApiUsage(
                          LocationStatsEnums.USAGE_STARTED,
                          LocationStatsEnums.API_REQUEST_GEOFENCE,
                          packageName,
                          request,
                          /* hasListener= */ false,
                          intent != null,
                          geofence,
                          mActivityManager.getPackageImportance(packageName));
              }
  
              mGeofenceManager.addFence(sanitizedRequest, geofence, intent,
                      allowedResolutionLevel,
                      uid, packageName);
          } finally {
              Binder.restoreCallingIdentity(identity);
          }
      }
  
      @Override
      public void removeGeofence(Geofence geofence, PendingIntent intent, String packageName) {
          if (intent == null) {
              throw new IllegalArgumentException("invalid pending intent: " + null);
          }
          checkPackageName(packageName);
  
          if (D) Log.d(TAG, "removeGeofence: " + geofence + " " + intent);
  
          // geo-fence manager uses the public location API, need to clear identity
          long identity = Binder.clearCallingIdentity();
          try {
              synchronized (mLock) {
                  mLocationUsageLogger.logLocationApiUsage(
                          LocationStatsEnums.USAGE_ENDED,
                          LocationStatsEnums.API_REQUEST_GEOFENCE,
                          packageName,
                          /* LocationRequest= */ null,
                          /* hasListener= */ false,
                          intent != null,
                          geofence,
                          mActivityManager.getPackageImportance(packageName));
              }
              mGeofenceManager.removeFence(geofence, intent);
          } finally {
              Binder.restoreCallingIdentity(identity);
          }
      }
  
      @Override
      public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName) {
          return addGnssDataListener(listener, packageName, "GnssStatusListener",
                  mGnssStatusProvider, mGnssStatusListeners,
                  this::unregisterGnssStatusCallback);
      }
  
      @Override
      public void unregisterGnssStatusCallback(IGnssStatusListener listener) {
          removeGnssDataListener(listener, mGnssStatusProvider, mGnssStatusListeners);
      }
  
      @Override
      public boolean addGnssMeasurementsListener(
              IGnssMeasurementsListener listener, String packageName) {
          return addGnssDataListener(listener, packageName, "GnssMeasurementsListener",
                  mGnssMeasurementsProvider, mGnssMeasurementsListeners,
                  this::removeGnssMeasurementsListener);
      }
  
      @Override
      public void removeGnssMeasurementsListener(IGnssMeasurementsListener listener) {
          removeGnssDataListener(listener, mGnssMeasurementsProvider, mGnssMeasurementsListeners);
      }
  
      private abstract static class LinkedListenerBase implements IBinder.DeathRecipient {
          protected final CallerIdentity mCallerIdentity;
          protected final String mListenerName;
  
          private LinkedListenerBase(@NonNull CallerIdentity callerIdentity,
                  @NonNull String listenerName) {
              mCallerIdentity = callerIdentity;
              mListenerName = listenerName;
          }
      }
  
      private static class LinkedListener<TListener> extends LinkedListenerBase {
          private final TListener mListener;
          private final Consumer<TListener> mBinderDeathCallback;
  
          private LinkedListener(@NonNull TListener listener, String listenerName,
                  @NonNull CallerIdentity callerIdentity,
                  @NonNull Consumer<TListener> binderDeathCallback) {
              super(callerIdentity, listenerName);
              mListener = listener;
              mBinderDeathCallback = binderDeathCallback;
          }
  
          @Override
          public void binderDied() {
              if (D) Log.d(TAG, "Remote " + mListenerName + " died.");
              mBinderDeathCallback.accept(mListener);
          }
      }
  
      private <TListener extends IInterface> boolean addGnssDataListener(
              TListener listener, String packageName, String listenerName,
              RemoteListenerHelper<TListener> gnssDataProvider,
              ArrayMap<IBinder, LinkedListener<TListener>> gnssDataListeners,
              Consumer<TListener> binderDeathCallback) {
          if (!hasGnssPermissions(packageName) || gnssDataProvider == null) {
              return false;
          }
  
          CallerIdentity callerIdentity = new CallerIdentity(Binder.getCallingUid(),
                  Binder.getCallingPid(), packageName);
          LinkedListener<TListener> linkedListener = new LinkedListener<>(listener,
                  listenerName, callerIdentity, binderDeathCallback);
          IBinder binder = listener.asBinder();
          synchronized (mLock) {
              if (!linkToListenerDeathNotificationLocked(binder, linkedListener)) {
                  return false;
              }
  
              gnssDataListeners.put(binder, linkedListener);
              long identity = Binder.clearCallingIdentity();
              try {
                  if (gnssDataProvider == mGnssMeasurementsProvider
                          || gnssDataProvider == mGnssStatusProvider) {
                      mLocationUsageLogger.logLocationApiUsage(
                              LocationStatsEnums.USAGE_STARTED,
                              gnssDataProvider == mGnssMeasurementsProvider
                                  ? LocationStatsEnums.API_ADD_GNSS_MEASUREMENTS_LISTENER
                                  : LocationStatsEnums.API_REGISTER_GNSS_STATUS_CALLBACK,
                              packageName,
                              /* LocationRequest= */ null,
                              /* hasListener= */ true,
                              /* hasIntent= */ false,
                              /* geofence= */ null,
                              mActivityManager.getPackageImportance(packageName));
                  }
                  if (isThrottlingExemptLocked(callerIdentity)
                          || isImportanceForeground(
                          mActivityManager.getPackageImportance(packageName))) {
                      gnssDataProvider.addListener(listener, callerIdentity);
                  }
                  return true;
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
      }
  
      private <TListener extends IInterface> void removeGnssDataListener(
              TListener listener, RemoteListenerHelper<TListener> gnssDataProvider,
              ArrayMap<IBinder, LinkedListener<TListener>> gnssDataListeners) {
          if (gnssDataProvider == null) {
              return;
          }
  
          IBinder binder = listener.asBinder();
          synchronized (mLock) {
              LinkedListener<TListener> linkedListener = gnssDataListeners.remove(binder);
              if (linkedListener == null) {
                  return;
              }
              long identity = Binder.clearCallingIdentity();
              try {
                  if (gnssDataProvider == mGnssMeasurementsProvider
                          || gnssDataProvider == mGnssStatusProvider) {
                      mLocationUsageLogger.logLocationApiUsage(
                              LocationStatsEnums.USAGE_ENDED,
                              gnssDataProvider == mGnssMeasurementsProvider
                                  ? LocationStatsEnums.API_ADD_GNSS_MEASUREMENTS_LISTENER
                                  : LocationStatsEnums.API_REGISTER_GNSS_STATUS_CALLBACK,
                              linkedListener.mCallerIdentity.mPackageName,
                              /* LocationRequest= */ null,
                              /* hasListener= */ true,
                              /* hasIntent= */ false,
                              /* geofence= */ null,
                              mActivityManager.getPackageImportance(
                                      linkedListener.mCallerIdentity.mPackageName));
                  }
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
              unlinkFromListenerDeathNotificationLocked(binder, linkedListener);
              gnssDataProvider.removeListener(listener);
          }
      }
  
      private boolean linkToListenerDeathNotificationLocked(IBinder binder,
              LinkedListenerBase linkedListener) {
          try {
              binder.linkToDeath(linkedListener, 0 /* flags */);
              return true;
          } catch (RemoteException e) {
              // if the remote process registering the listener is already dead, just swallow the
              // exception and return
              Log.w(TAG, "Could not link " + linkedListener.mListenerName + " death callback.", e);
              return false;
          }
      }
  
      private boolean unlinkFromListenerDeathNotificationLocked(IBinder binder,
              LinkedListenerBase linkedListener) {
          try {
              binder.unlinkToDeath(linkedListener, 0 /* flags */);
              return true;
          } catch (NoSuchElementException e) {
              // if the death callback isn't connected (it should be...), log error,
              // swallow the exception and return
              Log.w(TAG, "Could not unlink " + linkedListener.mListenerName + " death callback.", e);
              return false;
          }
      }
  
      @Override
      public void injectGnssMeasurementCorrections(
              GnssMeasurementCorrections measurementCorrections, String packageName) {
          mContext.enforceCallingPermission(
                  android.Manifest.permission.LOCATION_HARDWARE,
                  "Location Hardware permission not granted to inject GNSS measurement corrections.");
          if (!hasGnssPermissions(packageName)) {
              Slog.e(TAG, "Can not inject GNSS corrections due to no permission.");
              return;
          }
          if (mGnssMeasurementCorrectionsProvider == null) {
              Slog.e(TAG, "Can not inject GNSS corrections. GNSS measurement corrections provider "
                      + "not available.");
              return;
          }
          mGnssMeasurementCorrectionsProvider.injectGnssMeasurementCorrections(
                  measurementCorrections);
      }
  
      @Override
      public long getGnssCapabilities(String packageName) {
          mContext.enforceCallingPermission(
                  android.Manifest.permission.LOCATION_HARDWARE,
                  "Location Hardware permission not granted to obtain GNSS chipset capabilities.");
          if (!hasGnssPermissions(packageName) || mGnssCapabilitiesProvider == null) {
              return GnssCapabilities.INVALID_CAPABILITIES;
          }
          return mGnssCapabilitiesProvider.getGnssCapabilities();
      }
  
      @Override
      public boolean addGnssNavigationMessageListener(
              IGnssNavigationMessageListener listener, String packageName) {
          return addGnssDataListener(listener, packageName, "GnssNavigationMessageListener",
                  mGnssNavigationMessageProvider, mGnssNavigationMessageListeners,
                  this::removeGnssNavigationMessageListener);
      }
  
      @Override
      public void removeGnssNavigationMessageListener(IGnssNavigationMessageListener listener) {
          removeGnssDataListener(listener, mGnssNavigationMessageProvider,
                  mGnssNavigationMessageListeners);
      }
  
      @Override
      public boolean sendExtraCommand(String providerName, String command, Bundle extras) {
          if (providerName == null) {
              // throw NullPointerException to remain compatible with previous implementation
              throw new NullPointerException();
          }
          synchronized (mLock) {
              checkResolutionLevelIsSufficientForProviderUseLocked(getCallerAllowedResolutionLevel(),
                      providerName);
  
              mLocationUsageLogger.logLocationApiUsage(
                      LocationStatsEnums.USAGE_STARTED,
                      LocationStatsEnums.API_SEND_EXTRA_COMMAND,
                      providerName);
  
              // and check for ACCESS_LOCATION_EXTRA_COMMANDS
              if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS)
                      != PERMISSION_GRANTED)) {
                  throw new SecurityException("Requires ACCESS_LOCATION_EXTRA_COMMANDS permission");
              }
  
              LocationProvider provider = getLocationProviderLocked(providerName);
              if (provider != null) {
                  provider.sendExtraCommandLocked(command, extras);
              }
  
              mLocationUsageLogger.logLocationApiUsage(
                      LocationStatsEnums.USAGE_ENDED,
                      LocationStatsEnums.API_SEND_EXTRA_COMMAND,
                      providerName);
  
              return true;
          }
      }
  
      @Override
      public boolean sendNiResponse(int notifId, int userResponse) {
          if (Binder.getCallingUid() != Process.myUid()) {
              throw new SecurityException(
                      "calling sendNiResponse from outside of the system is not allowed");
          }
          try {
              return mNetInitiatedListener.sendNiResponse(notifId, userResponse);
          } catch (RemoteException e) {
              Slog.e(TAG, "RemoteException in LocationManagerService.sendNiResponse");
              return false;
          }
      }
  
      @Override
      public ProviderProperties getProviderProperties(String providerName) {
          synchronized (mLock) {
              checkResolutionLevelIsSufficientForProviderUseLocked(getCallerAllowedResolutionLevel(),
                      providerName);
  
              LocationProvider provider = getLocationProviderLocked(providerName);
              if (provider == null) {
                  return null;
              }
              return provider.getPropertiesLocked();
          }
      }
  
      @Override
      public boolean isProviderPackage(String packageName) {
          synchronized (mLock) {
              for (LocationProvider provider : mProviders) {
                  if (provider.getPackagesLocked().contains(packageName)) {
                      return true;
                  }
              }
  
              return false;
          }
      }
  
      @Override
      public void setExtraLocationControllerPackage(String packageName) {
          mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE,
                  Manifest.permission.LOCATION_HARDWARE + " permission required");
          synchronized (mLock) {
              mExtraLocationControllerPackage = packageName;
          }
      }
  
      @Override
      public String getExtraLocationControllerPackage() {
          synchronized (mLock) {
              return mExtraLocationControllerPackage;
          }
      }
  
      @Override
      public void setExtraLocationControllerPackageEnabled(boolean enabled) {
          mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE,
                  Manifest.permission.LOCATION_HARDWARE + " permission required");
          synchronized (mLock) {
              mExtraLocationControllerPackageEnabled = enabled;
          }
      }
  
      @Override
      public boolean isExtraLocationControllerPackageEnabled() {
          synchronized (mLock) {
              return mExtraLocationControllerPackageEnabled
                      && (mExtraLocationControllerPackage != null);
          }
      }
  
      private boolean isLocationEnabled() {
          return isLocationEnabledForUser(mCurrentUserId);
      }
  
      @Override
      public boolean isLocationEnabledForUser(int userId) {
          // Check INTERACT_ACROSS_USERS permission if userId is not current user id.
          if (UserHandle.getCallingUserId() != userId) {
              mContext.enforceCallingOrSelfPermission(
                      Manifest.permission.INTERACT_ACROSS_USERS,
                      "Requires INTERACT_ACROSS_USERS permission");
          }
  
          long identity = Binder.clearCallingIdentity();
          try {
              return Settings.Secure.getIntForUser(
                          mContext.getContentResolver(),
                          Settings.Secure.LOCATION_MODE,
                          Settings.Secure.LOCATION_MODE_OFF,
                          userId) != Settings.Secure.LOCATION_MODE_OFF;
          } finally {
              Binder.restoreCallingIdentity(identity);
          }
      }
  
      @Override
      public boolean isProviderEnabledForUser(String providerName, int userId) {
          // Check INTERACT_ACROSS_USERS permission if userId is not current user id.
          if (UserHandle.getCallingUserId() != userId) {
              mContext.enforceCallingOrSelfPermission(
                      Manifest.permission.INTERACT_ACROSS_USERS,
                      "Requires INTERACT_ACROSS_USERS permission");
          }
  
          // Fused provider is accessed indirectly via criteria rather than the provider-based APIs,
          // so we discourage its use
          if (FUSED_PROVIDER.equals(providerName)) return false;
  
          synchronized (mLock) {
              LocationProvider provider = getLocationProviderLocked(providerName);
              return provider != null && provider.isUseableForUserLocked(userId);
          }
      }
  
      @GuardedBy("mLock")
      private static boolean shouldBroadcastSafeLocked(
              Location loc, Location lastLoc, UpdateRecord record, long now) {
          // Always broadcast the first update
          if (lastLoc == null) {
              return true;
          }
  
          // Check whether sufficient time has passed
          long minTime = record.mRealRequest.getFastestInterval();
          long delta = (loc.getElapsedRealtimeNanos() - lastLoc.getElapsedRealtimeNanos())
                  / NANOS_PER_MILLI;
          if (delta < minTime - MAX_PROVIDER_SCHEDULING_JITTER_MS) {
              return false;
          }
  
          // Check whether sufficient distance has been traveled
          double minDistance = record.mRealRequest.getSmallestDisplacement();
          if (minDistance > 0.0) {
              if (loc.distanceTo(lastLoc) <= minDistance) {
                  return false;
              }
          }
  
          // Check whether sufficient number of udpates is left
          if (record.mRealRequest.getNumUpdates() <= 0) {
              return false;
          }
  
          // Check whether the expiry date has passed
          return record.mRealRequest.getExpireAt() >= now;
      }
  
      @GuardedBy("mLock")
      private void handleLocationChangedLocked(Location location, LocationProvider provider) {
          if (!mProviders.contains(provider)) {
              return;
          }
          if (!location.isComplete()) {
              Log.w(TAG, "Dropping incomplete location: " + location);
              return;
          }
  
          // only notify passive provider and update last location for locations that come from
          // useable providers
          if (provider.isUseableLocked()) {
              if (!provider.isPassiveLocked()) {
                  mPassiveProvider.updateLocation(location);
              }
          }
  
          if (D) Log.d(TAG, "incoming location: " + location);
          long now = SystemClock.elapsedRealtime();
          if (provider.isUseableLocked()) {
              updateLastLocationLocked(location, provider.getName());
          }
  
          // Update last known coarse interval location if enough time has passed.
          Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(
                  provider.getName());
          if (lastLocationCoarseInterval == null) {
              lastLocationCoarseInterval = new Location(location);
  
              if (provider.isUseableLocked()) {
                  mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval);
              }
          }
          long timeDiffNanos = location.getElapsedRealtimeNanos()
                  - lastLocationCoarseInterval.getElapsedRealtimeNanos();
          if (timeDiffNanos > LocationFudger.FASTEST_INTERVAL_MS * NANOS_PER_MILLI) {
              lastLocationCoarseInterval.set(location);
          }
          // Don't ever return a coarse location that is more recent than the allowed update
          // interval (i.e. don't allow an app to keep registering and unregistering for
          // location updates to overcome the minimum interval).
          Location noGPSLocation =
                  lastLocationCoarseInterval.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
  
          // Skip if there are no UpdateRecords for this provider.
          ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
          if (records == null || records.size() == 0) return;
  
          // Fetch coarse location
          Location coarseLocation = null;
          if (noGPSLocation != null) {
              coarseLocation = mLocationFudger.getOrCreate(noGPSLocation);
          }
  
          ArrayList<Receiver> deadReceivers = null;
          ArrayList<UpdateRecord> deadUpdateRecords = null;
  
          // Broadcast location to all listeners
          for (UpdateRecord r : records) {
              Receiver receiver = r.mReceiver;
              boolean receiverDead = false;
  
              if (!provider.isUseableLocked() && !isSettingsExemptLocked(r)) {
                  continue;
              }
  
              int receiverUserId = UserHandle.getUserId(receiver.mCallerIdentity.mUid);
              if (!isCurrentProfileLocked(receiverUserId)
                      && !isProviderPackage(receiver.mCallerIdentity.mPackageName)) {
                  if (D) {
                      Log.d(TAG, "skipping loc update for background user " + receiverUserId +
                              " (current user: " + mCurrentUserId + ", app: " +
                              receiver.mCallerIdentity.mPackageName + ")");
                  }
                  continue;
              }
  
              if (mBlacklist.isBlacklisted(receiver.mCallerIdentity.mPackageName)) {
                  if (D) {
                      Log.d(TAG, "skipping loc update for blacklisted app: " +
                              receiver.mCallerIdentity.mPackageName);
                  }
                  continue;
              }
  
              Location notifyLocation;
              if (receiver.mAllowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
                  notifyLocation = coarseLocation;  // use coarse location
              } else {
                  notifyLocation = location;  // use fine location
              }
              if (notifyLocation != null) {
                  Location lastLoc = r.mLastFixBroadcast;
                  if ((lastLoc == null)
                          || shouldBroadcastSafeLocked(notifyLocation, lastLoc, r, now)) {
                      if (lastLoc == null) {
                          lastLoc = new Location(notifyLocation);
                          r.mLastFixBroadcast = lastLoc;
                      } else {
                          lastLoc.set(notifyLocation);
                      }
                      // Report location access before delivering location to the client. This will
                      // note location delivery to appOps, so it should be called only when a
                      // location is really being delivered to the client.
                      if (!reportLocationAccessNoThrow(
                              receiver.mCallerIdentity.mPid,
                              receiver.mCallerIdentity.mUid,
                              receiver.mCallerIdentity.mPackageName,
                              receiver.mAllowedResolutionLevel)) {
                          if (D) {
                              Log.d(TAG, "skipping loc update for no op app: "
                                      + receiver.mCallerIdentity.mPackageName);
                          }
                          continue;
                      }
                      if (!receiver.callLocationChangedLocked(notifyLocation)) {
                          Slog.w(TAG, "RemoteException calling onLocationChanged on "
                                  + receiver);
                          receiverDead = true;
                      }
                      r.mRealRequest.decrementNumUpdates();
                  }
              }
  
              // TODO: location provider status callbacks have been disabled and deprecated, and are
              // guarded behind this setting now. should be removed completely post-Q
              if (Settings.Global.getInt(mContext.getContentResolver(),
                      LOCATION_DISABLE_STATUS_CALLBACKS, 1) == 0) {
                  long newStatusUpdateTime = provider.getStatusUpdateTimeLocked();
                  Bundle extras = new Bundle();
                  int status = provider.getStatusLocked(extras);
  
                  long prevStatusUpdateTime = r.mLastStatusBroadcast;
                  if ((newStatusUpdateTime > prevStatusUpdateTime)
                          && (prevStatusUpdateTime != 0 || status != AVAILABLE)) {
  
                      r.mLastStatusBroadcast = newStatusUpdateTime;
                      if (!receiver.callStatusChangedLocked(provider.getName(), status, extras)) {
                          receiverDead = true;
                          Slog.w(TAG, "RemoteException calling onStatusChanged on " + receiver);
                      }
                  }
              }
  
              // track expired records
              if (r.mRealRequest.getNumUpdates() <= 0 || r.mRealRequest.getExpireAt() < now) {
                  if (deadUpdateRecords == null) {
                      deadUpdateRecords = new ArrayList<>();
                  }
                  deadUpdateRecords.add(r);
              }
              // track dead receivers
              if (receiverDead) {
                  if (deadReceivers == null) {
                      deadReceivers = new ArrayList<>();
                  }
                  if (!deadReceivers.contains(receiver)) {
                      deadReceivers.add(receiver);
                  }
              }
          }
  
          // remove dead records and receivers outside the loop
          if (deadReceivers != null) {
              for (Receiver receiver : deadReceivers) {
                  removeUpdatesLocked(receiver);
              }
          }
          if (deadUpdateRecords != null) {
              for (UpdateRecord r : deadUpdateRecords) {
                  r.disposeLocked(true);
              }
              applyRequirementsLocked(provider);
          }
      }
  
      @GuardedBy("mLock")
      private void updateLastLocationLocked(Location location, String provider) {
          Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
          Location lastNoGPSLocation;
          Location lastLocation = mLastLocation.get(provider);
          if (lastLocation == null) {
              lastLocation = new Location(provider);
              mLastLocation.put(provider, lastLocation);
          } else {
              lastNoGPSLocation = lastLocation.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
              if (noGPSLocation == null && lastNoGPSLocation != null) {
                  // New location has no no-GPS location: adopt last no-GPS location. This is set
                  // directly into location because we do not want to notify COARSE clients.
                  location.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, lastNoGPSLocation);
              }
          }
          lastLocation.set(location);
      }
  
      // Geocoder
  
      @Override
      public boolean geocoderIsPresent() {
          return mGeocodeProvider != null;
      }
  
      @Override
      public String getFromLocation(double latitude, double longitude, int maxResults,
              GeocoderParams params, List<Address> addrs) {
          if (mGeocodeProvider != null) {
              return mGeocodeProvider.getFromLocation(latitude, longitude, maxResults,
                      params, addrs);
          }
          return null;
      }
  
  
      @Override
      public String getFromLocationName(String locationName,
              double lowerLeftLatitude, double lowerLeftLongitude,
              double upperRightLatitude, double upperRightLongitude, int maxResults,
              GeocoderParams params, List<Address> addrs) {
  
          if (mGeocodeProvider != null) {
              return mGeocodeProvider.getFromLocationName(locationName, lowerLeftLatitude,
                      lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
                      maxResults, params, addrs);
          }
          return null;
      }
  
      // Mock Providers
  
      private boolean canCallerAccessMockLocation(String opPackageName) {
          return mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(),
                  opPackageName) == AppOpsManager.MODE_ALLOWED;
      }
  
      @Override
      public void addTestProvider(String name, ProviderProperties properties, String opPackageName) {
          if (!canCallerAccessMockLocation(opPackageName)) {
              return;
          }
  
          if (PASSIVE_PROVIDER.equals(name)) {
              throw new IllegalArgumentException("Cannot mock the passive location provider");
          }
  
          synchronized (mLock) {
              long identity = Binder.clearCallingIdentity();
              try {
                  LocationProvider oldProvider = getLocationProviderLocked(name);
                  if (oldProvider != null) {
                      if (oldProvider.isMock()) {
                          throw new IllegalArgumentException(
                                  "Provider \"" + name + "\" already exists");
                      }
  
                      removeProviderLocked(oldProvider);
                  }
  
                  MockLocationProvider mockProviderManager = new MockLocationProvider(name);
                  addProviderLocked(mockProviderManager);
                  mockProviderManager.attachLocked(
                          new MockProvider(mContext, mockProviderManager, properties));
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
      }
  
      @Override
      public void removeTestProvider(String name, String opPackageName) {
          if (!canCallerAccessMockLocation(opPackageName)) {
              return;
          }
  
          synchronized (mLock) {
              long identity = Binder.clearCallingIdentity();
              try {
                  LocationProvider testProvider = getLocationProviderLocked(name);
                  if (testProvider == null || !testProvider.isMock()) {
                      throw new IllegalArgumentException("Provider \"" + name + "\" unknown");
                  }
  
                  removeProviderLocked(testProvider);
  
                  // reinstate real provider if available
                  LocationProvider realProvider = null;
                  for (LocationProvider provider : mRealProviders) {
                      if (name.equals(provider.getName())) {
                          realProvider = provider;
                          break;
                      }
                  }
  
                  if (realProvider != null) {
                      addProviderLocked(realProvider);
                  }
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
      }
  
      @Override
      public void setTestProviderLocation(String providerName, Location location,
              String opPackageName) {
          if (!canCallerAccessMockLocation(opPackageName)) {
              return;
          }
  
          synchronized (mLock) {
              LocationProvider testProvider = getLocationProviderLocked(providerName);
              if (testProvider == null || !testProvider.isMock()) {
                  throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
              }
  
              String locationProvider = location.getProvider();
              if (!TextUtils.isEmpty(locationProvider) && !providerName.equals(locationProvider)) {
                  // The location has an explicit provider that is different from the mock
                  // provider name. The caller may be trying to fool us via b/33091107.
                  EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(),
                          providerName + "!=" + location.getProvider());
              }
  
              ((MockLocationProvider) testProvider).setLocationLocked(location);
          }
      }
  
      @Override
      public void setTestProviderEnabled(String providerName, boolean enabled, String opPackageName) {
          if (!canCallerAccessMockLocation(opPackageName)) {
              return;
          }
  
          synchronized (mLock) {
              LocationProvider testProvider = getLocationProviderLocked(providerName);
              if (testProvider == null || !testProvider.isMock()) {
                  throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
              }
  
              ((MockLocationProvider) testProvider).setEnabledLocked(enabled);
          }
      }
  
      @Override
      public void setTestProviderStatus(String providerName, int status, Bundle extras,
              long updateTime, String opPackageName) {
          if (!canCallerAccessMockLocation(opPackageName)) {
              return;
          }
  
          synchronized (mLock) {
              LocationProvider testProvider = getLocationProviderLocked(providerName);
              if (testProvider == null || !testProvider.isMock()) {
                  throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
              }
  
              ((MockLocationProvider) testProvider).setStatusLocked(status, extras, updateTime);
          }
      }
  
      @Override
      @NonNull
      public List<LocationRequest> getTestProviderCurrentRequests(String providerName,
              String opPackageName) {
          if (!canCallerAccessMockLocation(opPackageName)) {
              return Collections.emptyList();
          }
  
          synchronized (mLock) {
              LocationProvider testProvider = getLocationProviderLocked(providerName);
              if (testProvider == null || !testProvider.isMock()) {
                  throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
              }
  
              MockLocationProvider provider = (MockLocationProvider) testProvider;
              if (provider.mCurrentRequest == null) {
                  return Collections.emptyList();
              }
              List<LocationRequest> requests = new ArrayList<>();
              for (LocationRequest request : provider.mCurrentRequest.locationRequests) {
                  requests.add(new LocationRequest(request));
              }
              return requests;
          }
      }
  
      @Override
      protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
          if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
  
          synchronized (mLock) {
              if (args.length > 0 && args[0].equals("--gnssmetrics")) {
                  if (mGnssMetricsProvider != null) {
                      pw.append(mGnssMetricsProvider.getGnssMetricsAsProtoString());
                  }
                  return;
              }
              pw.println("Current Location Manager state:");
              pw.print("  Current System Time: "
                      + TimeUtils.logTimeOfDay(System.currentTimeMillis()));
              pw.println(", Current Elapsed Time: "
                      + TimeUtils.formatDuration(SystemClock.elapsedRealtime()));
              pw.println("  Current user: " + mCurrentUserId + " " + Arrays.toString(
                      mCurrentUserProfiles));
              pw.println("  Location mode: " + isLocationEnabled());
              pw.println("  Battery Saver Location Mode: "
                      + locationPowerSaveModeToString(mBatterySaverMode));
              pw.println("  Location Listeners:");
              for (Receiver receiver : mReceivers.values()) {
                  pw.println("    " + receiver);
              }
              pw.println("  Active Records by Provider:");
              for (Map.Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) {
                  pw.println("    " + entry.getKey() + ":");
                  for (UpdateRecord record : entry.getValue()) {
                      pw.println("      " + record);
                  }
              }
  
              pw.println("  Active GnssMeasurement Listeners:");
              dumpGnssDataListenersLocked(pw, mGnssMeasurementsListeners);
              pw.println("  Active GnssNavigationMessage Listeners:");
              dumpGnssDataListenersLocked(pw, mGnssNavigationMessageListeners);
              pw.println("  Active GnssStatus Listeners:");
              dumpGnssDataListenersLocked(pw, mGnssStatusListeners);
  
              pw.println("  Historical Records by Provider:");
              for (Map.Entry<PackageProviderKey, PackageStatistics> entry
                      : mRequestStatistics.statistics.entrySet()) {
                  PackageProviderKey key = entry.getKey();
                  PackageStatistics stats = entry.getValue();
                  pw.println("    " + key.packageName + ": " + key.providerName + ": " + stats);
              }
              pw.println("  Last Known Locations:");
              for (Map.Entry<String, Location> entry : mLastLocation.entrySet()) {
                  String provider = entry.getKey();
                  Location location = entry.getValue();
                  pw.println("    " + provider + ": " + location);
              }
  
              pw.println("  Last Known Locations Coarse Intervals:");
              for (Map.Entry<String, Location> entry : mLastLocationCoarseInterval.entrySet()) {
                  String provider = entry.getKey();
                  Location location = entry.getValue();
                  pw.println("    " + provider + ": " + location);
              }
  
              if (mGeofenceManager != null) {
                  mGeofenceManager.dump(pw);
              } else {
                  pw.println("  Geofences: null");
              }
            
              if (mBlacklist != null) {
                  pw.append("  ");
                  mBlacklist.dump(pw);
              } else {
                  pw.println("  mBlacklist=null");
              }
  
              if (mExtraLocationControllerPackage != null) {
                  pw.println(" Location controller extra package: " + mExtraLocationControllerPackage
                          + " enabled: " + mExtraLocationControllerPackageEnabled);
              }
  
              if (!mBackgroundThrottlePackageWhitelist.isEmpty()) {
                  pw.println("  Throttling Whitelisted Packages:");
                  for (String packageName : mBackgroundThrottlePackageWhitelist) {
                      pw.println("    " + packageName);
                  }
              }
  
              if (!mIgnoreSettingsPackageWhitelist.isEmpty()) {
                  pw.println("  Bypass Whitelisted Packages:");
                  for (String packageName : mIgnoreSettingsPackageWhitelist) {
                      pw.println("    " + packageName);
                  }
              }
  
              if (mLocationFudger != null) {
                  pw.append("  fudger: ");
                  mLocationFudger.dump(fd, pw, args);
              } else {
                  pw.println("  fudger: null");
              }
  
              if (args.length > 0 && "short".equals(args[0])) {
                  return;
              }
              for (LocationProvider provider : mProviders) {
                  provider.dumpLocked(fd, pw, args);
              }
              if (mGnssBatchingInProgress) {
                  pw.println("  GNSS batching in progress");
              }
          }
      }
  
      @GuardedBy("mLock")
      private void dumpGnssDataListenersLocked(PrintWriter pw,
              ArrayMap<IBinder, ? extends LinkedListenerBase> gnssDataListeners) {
          for (LinkedListenerBase listener : gnssDataListeners.values()) {
              CallerIdentity callerIdentity = listener.mCallerIdentity;
              pw.println("    " + callerIdentity.mPid + " " + callerIdentity.mUid + " "
                      + callerIdentity.mPackageName + ": "
                      + isThrottlingExemptLocked(callerIdentity));
          }
      }
  }
  