diff --git a/services/core/java/com/android/server/GnssManagerService.java b/services/core/java/com/android/server/GnssManagerService.java
new file mode 100644
index 0000000000000000000000000000000000000000..b6e619effb08ce2c7078b20615c6c207e77403b5
--- /dev/null
+++ b/services/core/java/com/android/server/GnssManagerService.java
@@ -0,0 +1,800 @@
+/*
+ * Copyright (C) 2019 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 android.Manifest;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.content.Context;
+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.INetInitiatedListener;
+import android.location.Location;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Process;
+import android.os.RemoteException;
+import android.stats.location.LocationStatsEnums;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.LocationManagerServiceUtils.LinkedListener;
+import com.android.server.LocationManagerServiceUtils.LinkedListenerBase;
+import com.android.server.location.AbstractLocationProvider;
+import com.android.server.location.CallerIdentity;
+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.RemoteListenerHelper;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/** Manages Gnss providers and related Gnss functions for LocationManagerService. */
+public class GnssManagerService {
+    private static final String TAG = "LocationManagerService";
+    private static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
+
+    // Providers
+    private final GnssLocationProvider mGnssLocationProvider;
+    private final GnssStatusListenerHelper mGnssStatusProvider;
+    private final GnssMeasurementsProvider mGnssMeasurementsProvider;
+    private final GnssMeasurementCorrectionsProvider mGnssMeasurementCorrectionsProvider;
+    private final GnssNavigationMessageProvider mGnssNavigationMessageProvider;
+    private final GnssLocationProvider.GnssSystemInfoProvider mGnssSystemInfoProvider;
+    private final GnssLocationProvider.GnssMetricsProvider mGnssMetricsProvider;
+    private final GnssCapabilitiesProvider mGnssCapabilitiesProvider;
+    private final GnssBatchingProvider mGnssBatchingProvider;
+
+    private final INetInitiatedListener mNetInitiatedListener;
+    private final IGpsGeofenceHardware mGpsGeofenceProxy;
+    private final LocationManagerService mLocationManagerService;
+    private final LocationUsageLogger mLocationUsageLogger;
+
+    @GuardedBy("mGnssMeasurementsListeners")
+    private final ArrayMap<IBinder,
+            LinkedListener<IGnssMeasurementsListener>>
+            mGnssMeasurementsListeners = new ArrayMap<>();
+
+    @GuardedBy("mGnssNavigationMessageListeners")
+    private final ArrayMap<
+            IBinder, LinkedListener<IGnssNavigationMessageListener>>
+            mGnssNavigationMessageListeners = new ArrayMap<>();
+
+    @GuardedBy("mGnssStatusListeners")
+    private final ArrayMap<IBinder, LinkedListener<IGnssStatusListener>>
+            mGnssStatusListeners = new ArrayMap<>();
+
+    @GuardedBy("mGnssBatchingLock")
+    private IBatchedLocationCallback mGnssBatchingCallback;
+
+    @GuardedBy("mGnssBatchingLock")
+    private LinkedListener<IBatchedLocationCallback>
+            mGnssBatchingDeathCallback;
+
+    @GuardedBy("mGnssBatchingLock")
+    private boolean mGnssBatchingInProgress = false;
+
+    private final Object mGnssBatchingLock = new Object();
+    private final Context mContext;
+    private final Handler mHandler;
+
+    public GnssManagerService(LocationManagerService locationManagerService,
+            Context context,
+            AbstractLocationProvider.LocationProviderManager gnssProviderManager,
+            LocationUsageLogger locationUsageLogger) {
+        this(locationManagerService, context, new GnssLocationProvider(context, gnssProviderManager,
+                FgThread.getHandler().getLooper()), locationUsageLogger);
+    }
+
+    // Can use this constructor to inject GnssLocationProvider for testing
+    @VisibleForTesting
+    GnssManagerService(LocationManagerService locationManagerService,
+            Context context,
+            GnssLocationProvider gnssLocationProvider,
+            LocationUsageLogger locationUsageLogger) {
+        mContext = context;
+        mHandler = FgThread.getHandler();
+
+        mGnssLocationProvider =
+                gnssLocationProvider;
+
+        mGnssStatusProvider = mGnssLocationProvider.getGnssStatusProvider();
+        mGnssMeasurementsProvider = mGnssLocationProvider.getGnssMeasurementsProvider();
+        mGnssMeasurementCorrectionsProvider =
+                mGnssLocationProvider.getGnssMeasurementCorrectionsProvider();
+        mGnssNavigationMessageProvider = mGnssLocationProvider.getGnssNavigationMessageProvider();
+        mGnssSystemInfoProvider = mGnssLocationProvider.getGnssSystemInfoProvider();
+        mGnssMetricsProvider = mGnssLocationProvider.getGnssMetricsProvider();
+        mGnssCapabilitiesProvider = mGnssLocationProvider.getGnssCapabilitiesProvider();
+        mGnssBatchingProvider = mGnssLocationProvider.getGnssBatchingProvider();
+
+        mNetInitiatedListener = mGnssLocationProvider.getNetInitiatedListener();
+        mGpsGeofenceProxy = mGnssLocationProvider.getGpsGeofenceProxy();
+        mLocationManagerService = locationManagerService;
+        mLocationUsageLogger = locationUsageLogger;
+
+        registerUidListener();
+    }
+
+    public static boolean isGnssSupported() {
+        return GnssLocationProvider.isSupported();
+    }
+
+    private boolean hasGnssPermissions(String packageName) {
+        mContext.enforceCallingPermission(
+                Manifest.permission.ACCESS_FINE_LOCATION,
+                "Fine location permission not granted.");
+
+        int uid = Binder.getCallingUid();
+        long identity = Binder.clearCallingIdentity();
+        try {
+            return mContext.getSystemService(
+                    AppOpsManager.class).checkOp(AppOpsManager.OP_FINE_LOCATION, uid, packageName)
+                    == AppOpsManager.MODE_ALLOWED;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    public GnssLocationProvider getGnssLocationProvider() {
+        return mGnssLocationProvider;
+    }
+
+    public IGpsGeofenceHardware getGpsGeofenceProxy() {
+        return mGpsGeofenceProxy;
+    }
+
+    /**
+     * Get year of GNSS hardware.
+     *
+     * @return year of GNSS hardware as an int if possible, otherwise zero
+     */
+    public int getGnssYearOfHardware() {
+        if (mGnssSystemInfoProvider != null) {
+            return mGnssSystemInfoProvider.getGnssYearOfHardware();
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * Get model name of GNSS hardware.
+     *
+     * @return GNSS hardware model name as a string if possible, otherwise null
+     */
+    public String getGnssHardwareModelName() {
+        if (mGnssSystemInfoProvider != null) {
+            return mGnssSystemInfoProvider.getGnssHardwareModelName();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Get GNSS hardware capabilities. The capabilities are described in {@link
+     * android.location.GnssCapabilities} and their integer values correspond to the
+     * bit positions in the returned {@code long} value.
+     *
+     * @param packageName name of requesting package
+     * @return capabilities supported by the GNSS chipset
+     */
+    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();
+    }
+
+    /**
+     * Get size of GNSS batch (GNSS location results are batched together for power savings).
+     * Requires LOCATION_HARDWARE and GNSS permissions.
+     *
+     * @param packageName name of requesting package
+     * @return size of the GNSS batch collection
+     */
+    public int getGnssBatchSize(String packageName) {
+        mContext.enforceCallingPermission(
+                android.Manifest.permission.LOCATION_HARDWARE,
+                "Location Hardware permission not granted to access hardware batching");
+
+        if (!hasGnssPermissions(packageName)) {
+            Log.e(TAG, "getGnssBatchSize called without GNSS permissions");
+            return 0;
+        }
+        if (mGnssBatchingProvider == null) {
+            Log.e(
+                    TAG,
+                    "Can not get GNSS batch size. GNSS batching provider "
+                            + "not available.");
+            return 0;
+        }
+
+        synchronized (mGnssBatchingLock) {
+            return mGnssBatchingProvider.getBatchSize();
+        }
+    }
+
+    /**
+     * Starts GNSS batch collection. GNSS positions are collected in a batch before being delivered
+     * as a collection.
+     *
+     * @param periodNanos    duration over which to collect GPS positions before delivering as a
+     *                       batch
+     * @param wakeOnFifoFull specifying whether to wake on full queue
+     * @param packageName    name of requesting package
+     * @return true of batch started successfully, false otherwise
+     */
+    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)) {
+            Log.e(TAG, "startGnssBatch called without GNSS permissions");
+            return false;
+        }
+        if (mGnssBatchingProvider == null) {
+            Log.e(
+                    TAG,
+                    "Can not start GNSS batching. GNSS batching provider "
+                            + "not available.");
+            return false;
+        }
+
+        synchronized (mGnssBatchingLock) {
+            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);
+        }
+    }
+
+    /**
+     * Adds a GNSS batching callback for delivering GNSS location batch results.
+     *
+     * @param callback    called when batching operation is complete to deliver GPS positions
+     * @param packageName name of requesting package
+     * @return true if callback is successfully added, false otherwise
+     */
+    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)) {
+            Log.e(TAG, "addGnssBatchingCallback called without GNSS permissions");
+            return false;
+        }
+        if (mGnssBatchingProvider == null) {
+            Log.e(
+                    TAG,
+                    "Can not add GNSS batching callback. GNSS batching provider "
+                            + "not available.");
+            return false;
+        }
+
+        CallerIdentity callerIdentity =
+                new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName);
+        synchronized (mGnssBatchingLock) {
+            mGnssBatchingCallback = callback;
+            mGnssBatchingDeathCallback =
+                    new LocationManagerServiceUtils.LinkedListener<>(
+                            callback,
+                            "BatchedLocationCallback",
+                            callerIdentity,
+                            (IBatchedLocationCallback listener) -> {
+                                stopGnssBatch();
+                                removeGnssBatchingCallback();
+                            });
+            if (!mGnssBatchingDeathCallback.linkToListenerDeathNotificationLocked(
+                    callback.asBinder())) {
+                return false;
+            }
+            return true;
+        }
+    }
+
+    /**
+     * Force flush GNSS location results from batch.
+     *
+     * @param packageName name of requesting package
+     */
+    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;
+        }
+
+        if (mGnssBatchingProvider == null) {
+            Log.e(
+                    TAG,
+                    "Can not flush GNSS batch. GNSS batching provider "
+                            + "not available.");
+            return;
+        }
+
+        synchronized (mGnssBatchingLock) {
+            if (!mGnssBatchingInProgress) {
+                Log.w(TAG, "flushGnssBatch called with no batch in progress");
+            }
+            mGnssBatchingProvider.flush();
+        }
+    }
+
+    /**
+     * Removes GNSS batching callback.
+     */
+    public void removeGnssBatchingCallback() {
+        mContext.enforceCallingPermission(
+                android.Manifest.permission.LOCATION_HARDWARE,
+                "Location Hardware permission not granted to access hardware batching");
+
+        if (mGnssBatchingProvider == null) {
+            Log.e(
+                    TAG,
+                    "Can not add GNSS batching callback. GNSS batching provider "
+                            + "not available.");
+            return;
+        }
+
+        synchronized (mGnssBatchingLock) {
+            mGnssBatchingDeathCallback.unlinkFromListenerDeathNotificationLocked(
+                    mGnssBatchingCallback.asBinder());
+            mGnssBatchingCallback = null;
+            mGnssBatchingDeathCallback = null;
+        }
+    }
+
+    /**
+     * Stop GNSS batch collection.
+     *
+     * @return true if GNSS batch successfully stopped, false otherwise
+     */
+    public boolean stopGnssBatch() {
+        mContext.enforceCallingPermission(
+                android.Manifest.permission.LOCATION_HARDWARE,
+                "Location Hardware permission not granted to access hardware batching");
+
+        if (mGnssBatchingProvider == null) {
+            Log.e(
+                    TAG,
+                    "Can not stop GNSS batch. GNSS batching provider "
+                            + "not available.");
+            return false;
+        }
+        synchronized (mGnssBatchingLock) {
+            mGnssBatchingInProgress = false;
+            return mGnssBatchingProvider.stop();
+        }
+    }
+
+    private void registerUidListener() {
+        mContext.getSystemService(
+                ActivityManager.class).addOnUidImportanceListener(
+                    (uid, importance) -> {
+                        // listener invoked on ui thread, move to our thread to reduce risk
+                        // of blocking ui thread
+                        mHandler.post(
+                                () -> {
+                                    onForegroundChanged(uid,
+                                            LocationManagerServiceUtils.isImportanceForeground(
+                                                    importance));
+                                });
+                    },
+                ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE);
+    }
+
+    private void onForegroundChanged(int uid, boolean foreground) {
+        synchronized (mGnssMeasurementsListeners) {
+            updateListenersOnForegroundChangedLocked(
+                    mGnssMeasurementsListeners,
+                    mGnssMeasurementsProvider,
+                    IGnssMeasurementsListener.Stub::asInterface,
+                    uid,
+                    foreground);
+        }
+        synchronized (mGnssNavigationMessageListeners) {
+            updateListenersOnForegroundChangedLocked(
+                    mGnssNavigationMessageListeners,
+                    mGnssNavigationMessageProvider,
+                    IGnssNavigationMessageListener.Stub::asInterface,
+                    uid,
+                    foreground);
+        }
+        synchronized (mGnssStatusListeners) {
+            updateListenersOnForegroundChangedLocked(
+                    mGnssStatusListeners,
+                    mGnssStatusProvider,
+                    IGnssStatusListener.Stub::asInterface,
+                    uid,
+                    foreground);
+        }
+    }
+
+    private <TListener extends IInterface> void updateListenersOnForegroundChangedLocked(
+            ArrayMap<IBinder, ? extends LinkedListenerBase>
+                    gnssDataListeners,
+            RemoteListenerHelper<TListener> gnssDataProvider,
+            Function<IBinder, TListener> mapBinderToListener,
+            int uid,
+            boolean foreground) {
+        for (Map.Entry<IBinder, ? extends LinkedListenerBase> entry :
+                gnssDataListeners.entrySet()) {
+            LinkedListenerBase linkedListener = entry.getValue();
+            CallerIdentity callerIdentity = linkedListener.getCallerIdentity();
+            if (callerIdentity.mUid != uid) {
+                continue;
+            }
+
+            if (D) {
+                Log.d(
+                        TAG,
+                        linkedListener.getListenerName()
+                                + " from uid "
+                                + uid
+                                + " is now "
+                                + LocationManagerServiceUtils.foregroundAsString(foreground));
+            }
+
+            TListener listener = mapBinderToListener.apply(entry.getKey());
+            if (foreground || mLocationManagerService.isThrottlingExemptLocked(callerIdentity)) {
+                gnssDataProvider.addListener(listener, callerIdentity);
+            } else {
+                gnssDataProvider.removeListener(listener);
+            }
+        }
+    }
+
+    private <TListener extends IInterface> boolean addGnssDataListenerLocked(
+            TListener listener,
+            String packageName,
+            String listenerName,
+            RemoteListenerHelper<TListener> gnssDataProvider,
+            ArrayMap<IBinder,
+                    LinkedListener<TListener>> gnssDataListeners,
+            Consumer<TListener> binderDeathCallback) {
+        if (!hasGnssPermissions(packageName)) {
+            Log.e(TAG, "addGnssDataListenerLocked called without GNSS permissions");
+            return false;
+        }
+
+        if (gnssDataProvider == null) {
+            Log.e(
+                    TAG,
+                    "Can not add GNSS data listener. GNSS data provider "
+                            + "not available.");
+            return false;
+        }
+
+        CallerIdentity callerIdentity =
+                new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName);
+        LinkedListener<TListener> linkedListener =
+                new LocationManagerServiceUtils.LinkedListener<>(
+                        listener, listenerName, callerIdentity, binderDeathCallback);
+        IBinder binder = listener.asBinder();
+        if (!linkedListener.linkToListenerDeathNotificationLocked(binder)) {
+            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,
+                        LocationManagerServiceUtils.getPackageImportance(packageName,
+                                mContext));
+            }
+            if (mLocationManagerService.isThrottlingExemptLocked(callerIdentity)
+                    || LocationManagerServiceUtils.isImportanceForeground(
+                    LocationManagerServiceUtils.getPackageImportance(packageName, mContext))) {
+                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) {
+            Log.e(
+                    TAG,
+                    "Can not remove GNSS data listener. GNSS data provider "
+                            + "not available.");
+            return;
+        }
+
+        IBinder binder = listener.asBinder();
+        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.getCallerIdentity().mPackageName,
+                        /* LocationRequest= */ null,
+                        /* hasListener= */ true,
+                        /* hasIntent= */ false,
+                        /* geofence= */ null,
+                        LocationManagerServiceUtils.getPackageImportance(
+                                linkedListener.getCallerIdentity().mPackageName, mContext));
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        linkedListener.unlinkFromListenerDeathNotificationLocked(binder);
+        gnssDataProvider.removeListener(listener);
+    }
+
+    /**
+     * Registers listener for GNSS status changes.
+     *
+     * @param listener    called when GNSS status changes
+     * @param packageName name of requesting package
+     * @return true if listener is successfully registered, false otherwise
+     */
+    public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName) {
+        synchronized (mGnssStatusListeners) {
+            return addGnssDataListenerLocked(
+                    listener,
+                    packageName,
+                    "GnssStatusListener",
+                    mGnssStatusProvider,
+                    mGnssStatusListeners,
+                    this::unregisterGnssStatusCallback);
+        }
+    }
+
+    /**
+     * Unregisters listener for GNSS status changes.
+     *
+     * @param listener called when GNSS status changes
+     */
+    public void unregisterGnssStatusCallback(IGnssStatusListener listener) {
+        synchronized (mGnssStatusListeners) {
+            removeGnssDataListener(listener, mGnssStatusProvider, mGnssStatusListeners);
+        }
+    }
+
+    /**
+     * Adds a GNSS measurements listener.
+     *
+     * @param listener    called when GNSS measurements are received
+     * @param packageName name of requesting package
+     * @return true if listener is successfully added, false otherwise
+     */
+    public boolean addGnssMeasurementsListener(
+            IGnssMeasurementsListener listener, String packageName) {
+        synchronized (mGnssMeasurementsListeners) {
+            return addGnssDataListenerLocked(
+                    listener,
+                    packageName,
+                    "GnssMeasurementsListener",
+                    mGnssMeasurementsProvider,
+                    mGnssMeasurementsListeners,
+                    this::removeGnssMeasurementsListener);
+        }
+    }
+
+    /**
+     * Injects GNSS measurement corrections.
+     *
+     * @param measurementCorrections GNSS measurement corrections
+     * @param packageName            name of requesting package
+     */
+    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)) {
+            Log.e(TAG, "Can not inject GNSS corrections due to no permission.");
+            return;
+        }
+        if (mGnssMeasurementCorrectionsProvider == null) {
+            Log.e(
+                    TAG,
+                    "Can not inject GNSS corrections. GNSS measurement corrections provider "
+                            + "not available.");
+            return;
+        }
+        mGnssMeasurementCorrectionsProvider.injectGnssMeasurementCorrections(
+                measurementCorrections);
+    }
+
+    /**
+     * Removes a GNSS measurements listener.
+     *
+     * @param listener called when GNSS measurements are received
+     */
+    public void removeGnssMeasurementsListener(IGnssMeasurementsListener listener) {
+        synchronized (mGnssMeasurementsListeners) {
+            removeGnssDataListener(listener, mGnssMeasurementsProvider, mGnssMeasurementsListeners);
+        }
+    }
+
+    /**
+     * Adds a GNSS navigation message listener.
+     *
+     * @param listener    called when navigation message is received
+     * @param packageName name of requesting package
+     * @return true if listener is successfully added, false otherwise
+     */
+    public boolean addGnssNavigationMessageListener(
+            IGnssNavigationMessageListener listener, String packageName) {
+        synchronized (mGnssNavigationMessageListeners) {
+            return addGnssDataListenerLocked(
+                    listener,
+                    packageName,
+                    "GnssNavigationMessageListener",
+                    mGnssNavigationMessageProvider,
+                    mGnssNavigationMessageListeners,
+                    this::removeGnssNavigationMessageListener);
+        }
+    }
+
+    /**
+     * Removes a GNSS navigation message listener.
+     *
+     * @param listener called when navigation message is received
+     */
+    public void removeGnssNavigationMessageListener(IGnssNavigationMessageListener listener) {
+        removeGnssDataListener(
+                listener, mGnssNavigationMessageProvider, mGnssNavigationMessageListeners);
+    }
+
+    /**
+     * Send Ni Response, indicating a location request initiated by a network carrier.
+     */
+    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) {
+            Log.e(TAG, "RemoteException in LocationManagerService.sendNiResponse");
+            return false;
+        }
+    }
+
+    /**
+     * Report location results to GNSS batching listener.
+     *
+     * @param locations batch of locations to report to GNSS batching callback
+     */
+    public void onReportLocation(List<Location> locations) {
+        if (mGnssBatchingCallback == null) {
+            Log.e(TAG, "reportLocationBatch() called without active Callback");
+            return;
+        }
+
+        try {
+            mGnssBatchingCallback.onLocationBatch(locations);
+        } catch (RemoteException e) {
+            Log.e(TAG, "mGnssBatchingCallback.onLocationBatch failed", e);
+        }
+    }
+
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+
+        if (args.length > 0 && args[0].equals("--gnssmetrics")) {
+            if (mGnssMetricsProvider != null) {
+                pw.append(mGnssMetricsProvider.getGnssMetricsAsProtoString());
+            }
+            return;
+        }
+
+        ipw.println("GnssMeasurement Listeners:");
+        ipw.increaseIndent();
+        synchronized (mGnssMeasurementsListeners) {
+            for (LinkedListenerBase listener :
+                    mGnssMeasurementsListeners
+                            .values()) {
+                ipw.println(listener + ": " + mLocationManagerService.isThrottlingExemptLocked(
+                        listener.mCallerIdentity));
+            }
+        }
+        ipw.decreaseIndent();
+
+        ipw.println("GnssNavigationMessage Listeners:");
+        ipw.increaseIndent();
+        synchronized (mGnssNavigationMessageListeners) {
+            for (LinkedListenerBase listener :
+                    mGnssNavigationMessageListeners.values()) {
+                ipw.println(listener + ": " + mLocationManagerService.isThrottlingExemptLocked(
+                        listener.mCallerIdentity));
+            }
+        }
+        ipw.decreaseIndent();
+
+        ipw.println("GnssStatus Listeners:");
+        ipw.increaseIndent();
+        synchronized (mGnssStatusListeners) {
+            for (LinkedListenerBase listener :
+                    mGnssStatusListeners.values()) {
+                ipw.println(listener + ": " + mLocationManagerService.isThrottlingExemptLocked(
+                        listener.mCallerIdentity));
+            }
+        }
+        ipw.decreaseIndent();
+
+        synchronized (mGnssBatchingLock) {
+            if (mGnssBatchingInProgress) {
+                ipw.println("GNSS batching in progress");
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 69f226f67aa6cf836e046f632f8132d80dfa1bfc..2fcb7faabc3af639929febd07cb023ddf1fb0205 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -49,16 +49,13 @@ 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;
@@ -67,7 +64,6 @@ 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;
@@ -81,7 +77,6 @@ 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;
@@ -102,13 +97,6 @@ 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;
@@ -117,7 +105,6 @@ 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 com.android.server.pm.permission.PermissionManagerServiceInternal;
 
 import java.io.ByteArrayOutputStream;
@@ -132,9 +119,6 @@ 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
@@ -197,17 +181,13 @@ public class LocationManagerService extends ILocationManager.Stub {
     private GeofenceManager mGeofenceManager;
     private LocationFudger mLocationFudger;
     private GeocoderProxy mGeocodeProvider;
-    private GnssStatusListenerHelper mGnssStatusProvider;
-    private INetInitiatedListener mNetInitiatedListener;
-    private PassiveProvider mPassiveProvider;  // track passive provider for special cases
+    @Nullable
+    private GnssManagerService mGnssManagerService;
+    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")
@@ -239,32 +219,10 @@ public class LocationManagerService extends ILocationManager.Stub {
 
     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;
@@ -287,7 +245,7 @@ public class LocationManagerService extends ILocationManager.Stub {
                         com.android.internal.R.array.config_locationProviderPackageNames));
         permissionManagerInternal.setLocationExtraPackagesProvider(
                 userId -> mContext.getResources().getStringArray(
-                      com.android.internal.R.array.config_locationExtraPackageNames));
+                        com.android.internal.R.array.config_locationExtraPackageNames));
 
         // most startup is deferred until systemRunning()
     }
@@ -567,7 +525,7 @@ public class LocationManagerService extends ILocationManager.Stub {
 
     @GuardedBy("mLock")
     private void onUidImportanceChangedLocked(int uid, int importance) {
-        boolean foreground = isImportanceForeground(importance);
+        boolean foreground = LocationManagerServiceUtils.isImportanceForeground(importance);
         HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size());
         for (Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) {
             String provider = entry.getKey();
@@ -576,7 +534,8 @@ public class LocationManagerService extends ILocationManager.Stub {
                         && record.mIsForegroundUid != foreground) {
                     if (D) {
                         Log.d(TAG, "request from uid " + uid + " is now "
-                                + foregroundAsString(foreground));
+                                + LocationManagerServiceUtils.foregroundAsString(
+                                foreground));
                     }
                     record.updateForeground(foreground);
 
@@ -589,51 +548,6 @@ public class LocationManagerService extends ILocationManager.Stub {
         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")
@@ -768,28 +682,15 @@ public class LocationManagerService extends ILocationManager.Stub {
         mPassiveProvider = new PassiveProvider(mContext, passiveProviderManager);
         passiveProviderManager.attachLocked(mPassiveProvider);
 
-        if (GnssLocationProvider.isSupported()) {
-            // Create a gps location provider
+        if (GnssManagerService.isGnssSupported()) {
+            // Create a gps location provider manager
             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();
+            mGnssManagerService = new GnssManagerService(this, mContext, gnssProviderManager,
+                    mLocationUsageLogger);
+            gnssProviderManager.attachLocked(mGnssManagerService.getGnssLocationProvider());
         }
 
         /*
@@ -857,15 +758,17 @@ public class LocationManagerService extends ILocationManager.Stub {
             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.");
+        if (mGnssManagerService != null) {
+            // 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,
+                    mGnssManagerService.getGpsGeofenceProxy(),
+                    null);
+            if (provider == null) {
+                Slog.d(TAG, "Unable to bind FLP Geofence proxy.");
+            }
         }
 
         // bind to hardware activity recognition
@@ -939,7 +842,10 @@ public class LocationManagerService extends ILocationManager.Stub {
         }
     }
 
-    private class LocationProvider implements AbstractLocationProvider.LocationProviderManager {
+    /**
+     * Location provider manager, manages a LocationProvider.
+     */
+    class LocationProvider implements AbstractLocationProvider.LocationProviderManager {
 
         private final String mName;
 
@@ -948,7 +854,8 @@ public class LocationManagerService extends ILocationManager.Stub {
 
         // remember to clear binder identity before invoking any provider operation
         @GuardedBy("mLock")
-        @Nullable protected AbstractLocationProvider mProvider;
+        @Nullable
+        protected AbstractLocationProvider mProvider;
 
         @GuardedBy("mLock")
         private boolean mUseable;  // combined state
@@ -958,7 +865,8 @@ public class LocationManagerService extends ILocationManager.Stub {
         private boolean mEnabled;  // state of provider
 
         @GuardedBy("mLock")
-        @Nullable private ProviderProperties mProperties;
+        @Nullable
+        private ProviderProperties mProperties;
 
         private LocationProvider(String name) {
             this(name, false);
@@ -1095,6 +1003,9 @@ public class LocationManagerService extends ILocationManager.Stub {
 
         @Override
         public void onReportLocation(List<Location> locations) {
+            if (mGnssManagerService == null) {
+                return;
+            }
             synchronized (mLock) {
                 LocationProvider gpsProvider = getLocationProviderLocked(GPS_PROVIDER);
                 if (gpsProvider == null || !gpsProvider.isUseableLocked()) {
@@ -1102,16 +1013,7 @@ public class LocationManagerService extends ILocationManager.Stub {
                     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);
-                }
+                mGnssManagerService.onReportLocation(locations);
             }
         }
 
@@ -1300,7 +1202,8 @@ public class LocationManagerService extends ILocationManager.Stub {
      * A wrapper class holding either an ILocationListener or a PendingIntent to receive
      * location updates.
      */
-    private final class Receiver extends LinkedListenerBase implements PendingIntent.OnFinished {
+    private final class Receiver extends LocationManagerServiceUtils.LinkedListenerBase implements
+            PendingIntent.OnFinished {
         private static final long WAKELOCK_TIMEOUT_MILLIS = 60 * 1000;
         private final int mAllowedResolutionLevel;  // resolution level allowed to receiver
 
@@ -1614,146 +1517,45 @@ public class LocationManagerService extends ILocationManager.Stub {
 
     @Override
     public int getGnssYearOfHardware() {
-        if (mGnssSystemInfoProvider != null) {
-            return mGnssSystemInfoProvider.getGnssYearOfHardware();
-        } else {
-            return 0;
-        }
+        return mGnssManagerService == null ? 0 : mGnssManagerService.getGnssYearOfHardware();
     }
 
     @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);
-            }
-        }
+        return mGnssManagerService == null ? "" : mGnssManagerService.getGnssHardwareModelName();
     }
 
     @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;
-        }
+        return mGnssManagerService == null ? 0 : mGnssManagerService.getGnssBatchSize(packageName);
     }
 
     @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;
-        }
+        return mGnssManagerService == null ? false : mGnssManagerService.addGnssBatchingCallback(
+                callback, packageName);
     }
 
     @Override
     public void removeGnssBatchingCallback() {
-        synchronized (mLock) {
-            unlinkFromListenerDeathNotificationLocked(mGnssBatchingCallback.asBinder(),
-                    mGnssBatchingDeathCallback);
-            mGnssBatchingCallback = null;
-            mGnssBatchingDeathCallback = null;
-        }
+        if (mGnssManagerService != null) mGnssManagerService.removeGnssBatchingCallback();
     }
 
     @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);
-        }
+        return mGnssManagerService == null ? false : mGnssManagerService.startGnssBatch(periodNanos,
+                wakeOnFifoFull, packageName);
     }
 
     @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();
-            }
-        }
+        if (mGnssManagerService != null) mGnssManagerService.flushGnssBatch(packageName);
     }
 
     @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;
-            }
-        }
+        return mGnssManagerService == null ? false : mGnssManagerService.stopGnssBatch();
     }
 
     @GuardedBy("mLock")
@@ -2191,7 +1993,7 @@ public class LocationManagerService extends ILocationManager.Stub {
     }
 
     @GuardedBy("mLock")
-    private boolean isThrottlingExemptLocked(CallerIdentity callerIdentity) {
+    public boolean isThrottlingExemptLocked(CallerIdentity callerIdentity) {
         if (callerIdentity.mUid == Process.SYSTEM_UID) {
             return true;
         }
@@ -2236,8 +2038,10 @@ public class LocationManagerService extends ILocationManager.Stub {
             mRealRequest = request;
             mRequest = request;
             mReceiver = receiver;
-            mIsForegroundUid = isImportanceForeground(
-                    mActivityManager.getPackageImportance(mReceiver.mCallerIdentity.mPackageName));
+            mIsForegroundUid =
+                    LocationManagerServiceUtils.isImportanceForeground(
+                            mActivityManager.getPackageImportance(
+                                    mReceiver.mCallerIdentity.mPackageName));
 
             if (D && receiver.mCallerIdentity.mPid == Process.myPid()) {
                 mStackTrace = new Throwable();
@@ -2335,8 +2139,8 @@ public class LocationManagerService extends ILocationManager.Stub {
         if (receiver == null) {
             receiver = new Receiver(listener, null, pid, uid, packageName, workSource,
                     hideFromAppOps);
-            if (!linkToListenerDeathNotificationLocked(receiver.getListener().asBinder(),
-                    receiver)) {
+            if (!receiver.linkToListenerDeathNotificationLocked(
+                    receiver.getListener().asBinder())) {
                 return null;
             }
             mReceivers.put(binder, receiver);
@@ -2558,8 +2362,8 @@ public class LocationManagerService extends ILocationManager.Stub {
         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.unlinkFromListenerDeathNotificationLocked(
+                    receiver.getListener().asBinder());
             receiver.clearPendingBroadcastsLocked();
         }
 
@@ -2606,7 +2410,6 @@ public class LocationManagerService extends ILocationManager.Stub {
                     return null;
                 }
 
-
                 // Figure out the provider. Either its explicitly request (deprecated API's),
                 // or use the fused provider
                 String name = request.getProvider();
@@ -2666,7 +2469,7 @@ public class LocationManagerService extends ILocationManager.Stub {
                         if (D) {
                             Log.d(TAG, "not returning last loc for no op app: " + packageName);
                         }
-                        lastLocation =  null;
+                        lastLocation = null;
                     }
                 }
                 return lastLocation;
@@ -2807,219 +2610,58 @@ public class LocationManagerService extends ILocationManager.Stub {
 
     @Override
     public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName) {
-        return addGnssDataListener(listener, packageName, "GnssStatusListener",
-                mGnssStatusProvider, mGnssStatusListeners,
-                this::unregisterGnssStatusCallback);
+        return mGnssManagerService == null ? false : mGnssManagerService.registerGnssStatusCallback(
+                listener, packageName);
     }
 
     @Override
     public void unregisterGnssStatusCallback(IGnssStatusListener listener) {
-        removeGnssDataListener(listener, mGnssStatusProvider, mGnssStatusListeners);
+        if (mGnssManagerService != null) mGnssManagerService.unregisterGnssStatusCallback(listener);
     }
 
     @Override
     public boolean addGnssMeasurementsListener(
             IGnssMeasurementsListener listener, String packageName) {
-        return addGnssDataListener(listener, packageName, "GnssMeasurementsListener",
-                mGnssMeasurementsProvider, mGnssMeasurementsListeners,
-                this::removeGnssMeasurementsListener);
+        return mGnssManagerService == null ? false
+                : mGnssManagerService.addGnssMeasurementsListener(listener, packageName);
     }
 
     @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;
-        }
-
-        @Override
-        public String toString() {
-            return mListenerName + "[" + mCallerIdentity.mPackageName + "(" + mCallerIdentity.mPid
-                    + ")]";
-        }
-    }
-
-    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;
+        if (mGnssManagerService != null) {
+            mGnssManagerService.removeGnssMeasurementsListener(
+                    listener);
         }
     }
 
     @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;
+        if (mGnssManagerService != null) {
+            mGnssManagerService.injectGnssMeasurementCorrections(
+                    measurementCorrections, packageName);
         }
-        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();
+        return mGnssManagerService == null ? 0L : mGnssManagerService.getGnssCapabilities(
+                packageName);
     }
 
     @Override
     public boolean addGnssNavigationMessageListener(
             IGnssNavigationMessageListener listener, String packageName) {
-        return addGnssDataListener(listener, packageName, "GnssNavigationMessageListener",
-                mGnssNavigationMessageProvider, mGnssNavigationMessageListeners,
-                this::removeGnssNavigationMessageListener);
+        return mGnssManagerService == null ? false
+                : mGnssManagerService.addGnssNavigationMessageListener(listener, packageName);
     }
 
     @Override
     public void removeGnssNavigationMessageListener(IGnssNavigationMessageListener listener) {
-        removeGnssDataListener(listener, mGnssNavigationMessageProvider,
-                mGnssNavigationMessageListeners);
+        if (mGnssManagerService != null) {
+            mGnssManagerService.removeGnssNavigationMessageListener(
+                    listener);
+        }
     }
 
     @Override
@@ -3059,16 +2701,8 @@ public class LocationManagerService extends ILocationManager.Stub {
 
     @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;
-        }
+        return mGnssManagerService == null ? false : mGnssManagerService.sendNiResponse(notifId,
+                userResponse);
     }
 
     @Override
@@ -3149,10 +2783,10 @@ public class LocationManagerService extends ILocationManager.Stub {
         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;
+                    mContext.getContentResolver(),
+                    Settings.Secure.LOCATION_MODE,
+                    Settings.Secure.LOCATION_MODE_OFF,
+                    userId) != Settings.Secure.LOCATION_MODE_OFF;
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -3566,11 +3200,8 @@ public class LocationManagerService extends ILocationManager.Stub {
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
 
         synchronized (mLock) {
-            if (args.length > 0 && args[0].equals("--gnssmetrics")) {
-                if (mGnssMetricsProvider != null) {
-                    pw.append(mGnssMetricsProvider.getGnssMetricsAsProtoString());
-                }
-                return;
+            if (mGnssManagerService != null && args.length > 0 && args[0].equals("--gnssmetrics")) {
+                mGnssManagerService.dump(fd, pw, args);
             }
 
             ipw.println("Location Manager State:");
@@ -3604,27 +3235,6 @@ public class LocationManagerService extends ILocationManager.Stub {
             }
             ipw.decreaseIndent();
 
-            ipw.println("GnssMeasurement Listeners:");
-            ipw.increaseIndent();
-            for (LinkedListenerBase listener : mGnssMeasurementsListeners.values()) {
-                ipw.println(listener + ": " + isThrottlingExemptLocked(listener.mCallerIdentity));
-            }
-            ipw.decreaseIndent();
-
-            ipw.println("GnssNavigationMessage Listeners:");
-            ipw.increaseIndent();
-            for (LinkedListenerBase listener : mGnssNavigationMessageListeners.values()) {
-                ipw.println(listener + ": " + isThrottlingExemptLocked(listener.mCallerIdentity));
-            }
-            ipw.decreaseIndent();
-
-            ipw.println("GnssStatus Listeners:");
-            ipw.increaseIndent();
-            for (LinkedListenerBase listener : mGnssStatusListeners.values()) {
-                ipw.println(listener + ": " + isThrottlingExemptLocked(listener.mCallerIdentity));
-            }
-            ipw.decreaseIndent();
-
             ipw.println("Historical Records by Provider:");
             ipw.increaseIndent();
             for (Map.Entry<PackageProviderKey, PackageStatistics> entry
@@ -3654,7 +3264,7 @@ public class LocationManagerService extends ILocationManager.Stub {
                 mGeofenceManager.dump(ipw);
                 ipw.decreaseIndent();
             }
-          
+
             if (mBlacklist != null) {
                 mBlacklist.dump(ipw);
             }
@@ -3694,11 +3304,11 @@ public class LocationManagerService extends ILocationManager.Stub {
             for (LocationProvider provider : mProviders) {
                 provider.dumpLocked(fd, ipw, args);
             }
-            ipw.decreaseIndent();
+        }
 
-            if (mGnssBatchingInProgress) {
-                ipw.println("GNSS batching in progress");
-            }
+        if (mGnssManagerService != null) {
+            ipw.decreaseIndent();
+            mGnssManagerService.dump(fd, pw, args);
         }
     }
 }
diff --git a/services/core/java/com/android/server/LocationManagerServiceUtils.java b/services/core/java/com/android/server/LocationManagerServiceUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..9c8ac19cc591b082e0a167958c311386b99851eb
--- /dev/null
+++ b/services/core/java/com/android/server/LocationManagerServiceUtils.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2019 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 android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.server.location.CallerIdentity;
+
+import java.util.NoSuchElementException;
+import java.util.function.Consumer;
+
+/**
+ * Shared utilities for LocationManagerService and GnssManager.
+ */
+public class LocationManagerServiceUtils {
+
+    private static final String TAG = "LocManagerServiceUtils";
+    private static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
+
+    /**
+     * Listener that can be linked to a binder.
+     * @param <TListener> listener type
+     */
+    public static class LinkedListener<TListener> extends
+            LinkedListenerBase {
+        private final TListener mListener;
+        private final Consumer<TListener> mBinderDeathCallback;
+
+        public 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);
+        }
+    }
+
+    /**
+     * Skeleton class of listener that can be linked to a binder.
+     */
+    public abstract static class LinkedListenerBase implements IBinder.DeathRecipient {
+        protected final CallerIdentity mCallerIdentity;
+        protected final String mListenerName;
+
+        LinkedListenerBase(
+                @NonNull CallerIdentity callerIdentity, @NonNull String listenerName) {
+            mCallerIdentity = callerIdentity;
+            mListenerName = listenerName;
+        }
+
+        @Override
+        public String toString() {
+            return mListenerName + "[" + mCallerIdentity.mPackageName + "(" + mCallerIdentity.mPid
+                    + ")]";
+        }
+
+        public CallerIdentity getCallerIdentity() {
+            return mCallerIdentity;
+        }
+
+        public String getListenerName() {
+            return mListenerName;
+        }
+
+        /**
+         * Link listener (i.e. callback) to a binder, so that it will be called upon binder's death.
+         *
+         * @param binder that calls listener upon death
+         * @return true if listener is successfully linked to binder, false otherwise
+         */
+        public boolean linkToListenerDeathNotificationLocked(
+                IBinder binder) {
+            try {
+                binder.linkToDeath(this, 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 " + mListenerName + " death callback.", e);
+                return false;
+            }
+        }
+
+        /**
+         * Unlink death listener (i.e. callback) from binder.
+         *
+         * @param binder that calls listener upon death
+         * @return true if binder is successfully unlinked from binder, false otherwise
+         */
+        public boolean unlinkFromListenerDeathNotificationLocked(
+                IBinder binder) {
+            try {
+                binder.unlinkToDeath(this, 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 " + mListenerName + " death callback.", e);
+                return false;
+            }
+        }
+
+    }
+
+    /**
+     * Convert boolean foreground into "foreground" or "background" string.
+     *
+     * @param foreground boolean indicating foreground
+     * @return "foreground" string if true, false otherwise
+     */
+    public static String foregroundAsString(boolean foreground) {
+        return foreground ? "foreground" : "background";
+    }
+
+
+    /**
+     * Classifies importance level as foreground or not.
+     *
+     * @param importance level as int
+     * @return boolean indicating if importance level is foreground or greater
+     */
+    public static boolean isImportanceForeground(int importance) {
+        return importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+    }
+
+    /**
+     * Get package importance level.
+     *
+     * @param packageName package name
+     * @return package importance level as int
+     */
+    public static int getPackageImportance(String packageName, Context context) {
+        return ((ActivityManager) context.getSystemService(
+                Context.ACTIVITY_SERVICE)).getPackageImportance(packageName);
+    }
+}