All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.server.location.GnssLocationProvider Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * Copyright (C) 2008 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.location;

import android.app.AlarmManager;
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
import android.hardware.location.GeofenceHardware;
import android.hardware.location.GeofenceHardwareImpl;
import android.location.Criteria;
import android.location.FusedBatchOptions;
import android.location.GnssMeasurementsEvent;
import android.location.GnssNavigationMessage;
import android.location.GnssStatus;
import android.location.IGpsGeofenceHardware;
import android.location.INetInitiatedListener;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.location.LocationRequest;
import android.os.AsyncTask;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
import android.os.PowerManager;
import android.os.PowerManager.ServiceType;
import android.os.PowerSaveState;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.WorkSource;
import android.os.WorkSource.WorkChain;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.gsm.GsmCellLocation;
import android.text.TextUtils;
import android.util.Log;
import android.util.StatsLog;
import android.util.TimeUtils;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IBatteryStats;
import com.android.internal.location.GpsNetInitiatedHandler;
import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification;
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
import com.android.internal.location.gnssmetrics.GnssMetrics;
import com.android.internal.telephony.TelephonyIntents;
import com.android.server.location.GnssSatelliteBlacklistHelper.GnssSatelliteBlacklistCallback;
import com.android.server.location.NtpTimeHelper.InjectNtpTimeCallback;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * A GNSS implementation of LocationProvider used by LocationManager.
 *
 * {@hide}
 */
public class GnssLocationProvider extends AbstractLocationProvider implements
        InjectNtpTimeCallback,
        GnssSatelliteBlacklistCallback {

    /**
     * Indicates that this method is a native entry point. Useful purely for IDEs which can
     * understand entry points, and thus eliminate incorrect warnings about methods not used.
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.SOURCE)
    private @interface NativeEntryPoint {
    }

    private static final String TAG = "GnssLocationProvider";

    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);

    private static final ProviderProperties PROPERTIES = new ProviderProperties(
            true, true, false, false, true, true, true,
            Criteria.POWER_HIGH, Criteria.ACCURACY_FINE);

    // these need to match GnssPositionMode enum in IGnss.hal
    private static final int GPS_POSITION_MODE_STANDALONE = 0;
    private static final int GPS_POSITION_MODE_MS_BASED = 1;
    private static final int GPS_POSITION_MODE_MS_ASSISTED = 2;

    // these need to match GnssPositionRecurrence enum in IGnss.hal
    private static final int GPS_POSITION_RECURRENCE_PERIODIC = 0;
    private static final int GPS_POSITION_RECURRENCE_SINGLE = 1;

    // these need to match GnssStatusValue enum in IGnssCallback.hal
    private static final int GPS_STATUS_NONE = 0;
    private static final int GPS_STATUS_SESSION_BEGIN = 1;
    private static final int GPS_STATUS_SESSION_END = 2;
    private static final int GPS_STATUS_ENGINE_ON = 3;
    private static final int GPS_STATUS_ENGINE_OFF = 4;

    // these need to match GnssLocationFlags enum in types.hal
    private static final int LOCATION_INVALID = 0;
    private static final int LOCATION_HAS_LAT_LONG = 1;
    private static final int LOCATION_HAS_ALTITUDE = 2;
    private static final int LOCATION_HAS_SPEED = 4;
    private static final int LOCATION_HAS_BEARING = 8;
    private static final int LOCATION_HAS_HORIZONTAL_ACCURACY = 16;
    private static final int LOCATION_HAS_VERTICAL_ACCURACY = 32;
    private static final int LOCATION_HAS_SPEED_ACCURACY = 64;
    private static final int LOCATION_HAS_BEARING_ACCURACY = 128;

    // these need to match ElapsedRealtimeFlags enum in types.hal
    private static final int ELAPSED_REALTIME_HAS_TIMESTAMP_NS = 1;
    private static final int ELAPSED_REALTIME_HAS_TIME_UNCERTAINTY_NS = 2;

    // IMPORTANT - the GPS_DELETE_* symbols here must match GnssAidingData enum in IGnss.hal
    private static final int GPS_DELETE_EPHEMERIS = 0x0001;
    private static final int GPS_DELETE_ALMANAC = 0x0002;
    private static final int GPS_DELETE_POSITION = 0x0004;
    private static final int GPS_DELETE_TIME = 0x0008;
    private static final int GPS_DELETE_IONO = 0x0010;
    private static final int GPS_DELETE_UTC = 0x0020;
    private static final int GPS_DELETE_HEALTH = 0x0040;
    private static final int GPS_DELETE_SVDIR = 0x0080;
    private static final int GPS_DELETE_SVSTEER = 0x0100;
    private static final int GPS_DELETE_SADATA = 0x0200;
    private static final int GPS_DELETE_RTI = 0x0400;
    private static final int GPS_DELETE_CELLDB_INFO = 0x8000;
    private static final int GPS_DELETE_ALL = 0xFFFF;

    // The GPS_CAPABILITY_* flags must match Capabilities enum in IGnssCallback.hal
    private static final int GPS_CAPABILITY_SCHEDULING = 0x0000001;
    private static final int GPS_CAPABILITY_MSB = 0x0000002;
    private static final int GPS_CAPABILITY_MSA = 0x0000004;
    private static final int GPS_CAPABILITY_SINGLE_SHOT = 0x0000008;
    private static final int GPS_CAPABILITY_ON_DEMAND_TIME = 0x0000010;
    public static final int GPS_CAPABILITY_GEOFENCING = 0x0000020;
    public static final int GPS_CAPABILITY_MEASUREMENTS = 0x0000040;
    public static final int GPS_CAPABILITY_NAV_MESSAGES = 0x0000080;
    public static final int GPS_CAPABILITY_LOW_POWER_MODE = 0x0000100;
    public static final int GPS_CAPABILITY_SATELLITE_BLACKLIST = 0x0000200;
    public static final int GPS_CAPABILITY_MEASUREMENT_CORRECTIONS = 0x0000400;

    // The AGPS SUPL mode
    private static final int AGPS_SUPL_MODE_MSA = 0x02;
    private static final int AGPS_SUPL_MODE_MSB = 0x01;

    private static final int SET_REQUEST = 3;
    private static final int INJECT_NTP_TIME = 5;
    // PSDS stands for Predicted Satellite Data Service
    private static final int DOWNLOAD_PSDS_DATA = 6;
    private static final int UPDATE_LOCATION = 7;  // Handle external location from network listener
    private static final int DOWNLOAD_PSDS_DATA_FINISHED = 11;
    private static final int INITIALIZE_HANDLER = 13;
    private static final int REQUEST_LOCATION = 16;
    private static final int REPORT_LOCATION = 17; // HAL reports location
    private static final int REPORT_SV_STATUS = 18; // HAL reports SV status

    // Request setid
    private static final int AGPS_RIL_REQUEST_SETID_IMSI = 1;
    private static final int AGPS_RIL_REQUEST_SETID_MSISDN = 2;

    // ref. location info
    private static final int AGPS_REF_LOCATION_TYPE_GSM_CELLID = 1;
    private static final int AGPS_REF_LOCATION_TYPE_UMTS_CELLID = 2;

    // set id info
    private static final int AGPS_SETID_TYPE_NONE = 0;
    private static final int AGPS_SETID_TYPE_IMSI = 1;
    private static final int AGPS_SETID_TYPE_MSISDN = 2;

    private static final int GPS_GEOFENCE_UNAVAILABLE = 1 << 0L;
    private static final int GPS_GEOFENCE_AVAILABLE = 1 << 1L;

    // GPS Geofence errors. Should match GeofenceStatus enum in IGnssGeofenceCallback.hal.
    private static final int GPS_GEOFENCE_OPERATION_SUCCESS = 0;
    private static final int GPS_GEOFENCE_ERROR_TOO_MANY_GEOFENCES = 100;
    private static final int GPS_GEOFENCE_ERROR_ID_EXISTS = -101;
    private static final int GPS_GEOFENCE_ERROR_ID_UNKNOWN = -102;
    private static final int GPS_GEOFENCE_ERROR_INVALID_TRANSITION = -103;
    private static final int GPS_GEOFENCE_ERROR_GENERIC = -149;

    // TCP/IP constants.
    // Valid TCP/UDP port range is (0, 65535].
    private static final int TCP_MIN_PORT = 0;
    private static final int TCP_MAX_PORT = 0xffff;

    // 1 second, or 1 Hz frequency.
    private static final long LOCATION_UPDATE_MIN_TIME_INTERVAL_MILLIS = 1000;
    // Default update duration in milliseconds for REQUEST_LOCATION.
    private static final long LOCATION_UPDATE_DURATION_MILLIS = 10 * 1000;
    // Update duration extension multiplier for emergency REQUEST_LOCATION.
    private static final int EMERGENCY_LOCATION_UPDATE_DURATION_MULTIPLIER = 3;

    /** simpler wrapper for ProviderRequest + Worksource */
    private static class GpsRequest {
        public ProviderRequest request;
        public WorkSource source;

        public GpsRequest(ProviderRequest request, WorkSource source) {
            this.request = request;
            this.source = source;
        }
    }

    // Threadsafe class to hold stats reported in the Extras Bundle
    private static class LocationExtras {
        private int mSvCount;
        private int mMeanCn0;
        private int mMaxCn0;
        private final Bundle mBundle;

        public LocationExtras() {
            mBundle = new Bundle();
        }

        public void set(int svCount, int meanCn0, int maxCn0) {
            synchronized (this) {
                mSvCount = svCount;
                mMeanCn0 = meanCn0;
                mMaxCn0 = maxCn0;
            }
            setBundle(mBundle);
        }

        public void reset() {
            set(0, 0, 0);
        }

        // Also used by outside methods to add to other bundles
        public void setBundle(Bundle extras) {
            if (extras != null) {
                synchronized (this) {
                    extras.putInt("satellites", mSvCount);
                    extras.putInt("meanCn0", mMeanCn0);
                    extras.putInt("maxCn0", mMaxCn0);
                }
            }
        }

        public Bundle getBundle() {
            synchronized (this) {
                return new Bundle(mBundle);
            }
        }
    }

    private final Object mLock = new Object();

    // current status
    private int mStatus = LocationProvider.TEMPORARILY_UNAVAILABLE;

    // time for last status update
    private long mStatusUpdateTime = SystemClock.elapsedRealtime();

    // turn off GPS fix icon if we haven't received a fix in 10 seconds
    private static final long RECENT_FIX_TIMEOUT = 10 * 1000;

    // stop trying if we do not receive a fix within 60 seconds
    private static final int NO_FIX_TIMEOUT = 60 * 1000;

    // if the fix interval is below this we leave GPS on,
    // if above then we cycle the GPS driver.
    // Typical hot TTTF is ~5 seconds, so 10 seconds seems sane.
    private static final int GPS_POLLING_THRESHOLD_INTERVAL = 10 * 1000;

    // how long to wait if we have a network error in NTP or PSDS downloading
    // the initial value of the exponential backoff
    // current setting - 5 minutes
    private static final long RETRY_INTERVAL = 5 * 60 * 1000;
    // how long to wait if we have a network error in NTP or PSDS downloading
    // the max value of the exponential backoff
    // current setting - 4 hours
    private static final long MAX_RETRY_INTERVAL = 4 * 60 * 60 * 1000;

    // Timeout when holding wakelocks for downloading PSDS data.
    private static final long DOWNLOAD_PSDS_DATA_TIMEOUT_MS = 60 * 1000;

    private final ExponentialBackOff mPsdsBackOff = new ExponentialBackOff(RETRY_INTERVAL,
            MAX_RETRY_INTERVAL);

    // True if we are enabled
    @GuardedBy("mLock")
    private boolean mGpsEnabled;

    private boolean mShutdown;

    // states for injecting ntp and downloading psds data
    private static final int STATE_PENDING_NETWORK = 0;
    private static final int STATE_DOWNLOADING = 1;
    private static final int STATE_IDLE = 2;

    // flags to trigger NTP or PSDS data download when network becomes available
    // initialized to true so we do NTP and PSDS when the network comes up after booting
    private int mDownloadPsdsDataPending = STATE_PENDING_NETWORK;

    // true if GPS is navigating
    private boolean mNavigating;

    // requested frequency of fixes, in milliseconds
    private int mFixInterval = 1000;

    // true if low power mode for the GNSS chipset is part of the latest request.
    private boolean mLowPowerMode = false;

    // true if we started navigation in the HAL, only change value of this in setStarted
    private boolean mStarted;

    // for logging of latest change, and warning of ongoing location after a stop
    private long mStartedChangedElapsedRealtime;

    // threshold for delay in GNSS engine turning off before warning & error
    private static final long LOCATION_OFF_DELAY_THRESHOLD_WARN_MILLIS = 2 * 1000;
    private static final long LOCATION_OFF_DELAY_THRESHOLD_ERROR_MILLIS = 15 * 1000;

    // capabilities reported through the top level IGnssCallback.hal
    private volatile int mTopHalCapabilities;

    // true if PSDS is supported
    private boolean mSupportsPsds;

    // for calculating time to first fix
    private long mFixRequestTime = 0;
    // time to first fix for most recent session
    private int mTimeToFirstFix = 0;
    // time we received our last fix
    private long mLastFixTime;

    private int mPositionMode;
    private GnssPositionMode mLastPositionMode;

    // Current request from underlying location clients.
    private ProviderRequest mProviderRequest;
    // The WorkSource associated with the most recent client request (i.e, most recent call to
    // setRequest).
    private WorkSource mWorkSource = null;
    // True if gps should be disabled because of PowerManager controls
    private boolean mDisableGpsForPowerManager = false;

    /**
     * Properties loaded from PROPERTIES_FILE.
     * It must be accessed only inside {@link #mHandler}.
     */
    private GnssConfiguration mGnssConfiguration;

    private String mSuplServerHost;
    private int mSuplServerPort = TCP_MIN_PORT;
    private String mC2KServerHost;
    private int mC2KServerPort;
    private boolean mSuplEsEnabled = false;

    private final Looper mLooper;
    private final LocationExtras mLocationExtras = new LocationExtras();
    private final GnssStatusListenerHelper mGnssStatusListenerHelper;
    private final GnssMeasurementsProvider mGnssMeasurementsProvider;
    private final GnssMeasurementCorrectionsProvider mGnssMeasurementCorrectionsProvider;
    private final GnssNavigationMessageProvider mGnssNavigationMessageProvider;
    private final LocationChangeListener mNetworkLocationListener = new NetworkLocationListener();
    private final LocationChangeListener mFusedLocationListener = new FusedLocationListener();
    private final NtpTimeHelper mNtpTimeHelper;
    private final GnssBatchingProvider mGnssBatchingProvider;
    private final GnssGeofenceProvider mGnssGeofenceProvider;
    private final GnssCapabilitiesProvider mGnssCapabilitiesProvider;

    // Available only on GNSS HAL 2.0 implementations and later.
    private GnssVisibilityControl mGnssVisibilityControl;

    // Handler for processing events
    private Handler mHandler;

    private final GnssNetworkConnectivityHandler mNetworkConnectivityHandler;
    private final GpsNetInitiatedHandler mNIHandler;

    // Wakelocks
    private final static String WAKELOCK_KEY = "GnssLocationProvider";
    private final PowerManager.WakeLock mWakeLock;
    private static final String DOWNLOAD_EXTRA_WAKELOCK_KEY = "GnssLocationProviderPsdsDownload";
    @GuardedBy("mLock")
    private final PowerManager.WakeLock mDownloadPsdsWakeLock;

    // Alarms
    private final static String ALARM_WAKEUP = "com.android.internal.location.ALARM_WAKEUP";
    private final static String ALARM_TIMEOUT = "com.android.internal.location.ALARM_TIMEOUT";

    private final PowerManager mPowerManager;
    private final AlarmManager mAlarmManager;
    private final PendingIntent mWakeupIntent;
    private final PendingIntent mTimeoutIntent;

    private final AppOpsManager mAppOps;
    private final IBatteryStats mBatteryStats;

    // Current list of underlying location clients.
    // only modified on handler thread
    private WorkSource mClientSource = new WorkSource();

    private GeofenceHardwareImpl mGeofenceHardwareImpl;

    // Volatile for simple inter-thread sync on these values.
    private volatile int mHardwareYear = 0;
    private volatile String mHardwareModelName;

    // Set lower than the current ITAR limit of 600m/s to allow this to trigger even if GPS HAL
    // stops output right at 600m/s, depriving this of the information of a device that reaches
    // greater than 600m/s, and higher than the speed of sound to avoid impacting most use cases.
    private static final float ITAR_SPEED_LIMIT_METERS_PER_SECOND = 400.0F;

    private volatile boolean mItarSpeedLimitExceeded = false;

    // GNSS Metrics
    private GnssMetrics mGnssMetrics;

    public GnssStatusListenerHelper getGnssStatusProvider() {
        return mGnssStatusListenerHelper;
    }

    public IGpsGeofenceHardware getGpsGeofenceProxy() {
        return mGnssGeofenceProvider;
    }

    public GnssMeasurementsProvider getGnssMeasurementsProvider() {
        return mGnssMeasurementsProvider;
    }

    public GnssMeasurementCorrectionsProvider getGnssMeasurementCorrectionsProvider() {
        return mGnssMeasurementCorrectionsProvider;
    }

    public GnssNavigationMessageProvider getGnssNavigationMessageProvider() {
        return mGnssNavigationMessageProvider;
    }
    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (DEBUG) Log.d(TAG, "receive broadcast intent, action: " + action);
            if (action == null) {
                return;
            }

            switch (action) {
                case ALARM_WAKEUP:
                    startNavigating();
                    break;
                case ALARM_TIMEOUT:
                    hibernate();
                    break;
                case PowerManager.ACTION_POWER_SAVE_MODE_CHANGED:
                case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
                case Intent.ACTION_SCREEN_OFF:
                case Intent.ACTION_SCREEN_ON:
                    updateLowPowerMode();
                    break;
                case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
                case TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
                    subscriptionOrCarrierConfigChanged(context);
                    break;
            }
        }
    };

    /**
     * Implements {@link GnssSatelliteBlacklistCallback#onUpdateSatelliteBlacklist}.
     */
    @Override
    public void onUpdateSatelliteBlacklist(int[] constellations, int[] svids) {
        mHandler.post(() -> mGnssConfiguration.setSatelliteBlacklist(constellations, svids));
        mGnssMetrics.resetConstellationTypes();
    }

    private void subscriptionOrCarrierConfigChanged(Context context) {
        if (DEBUG) Log.d(TAG, "received SIM related action: ");
        TelephonyManager phone = (TelephonyManager)
                mContext.getSystemService(Context.TELEPHONY_SERVICE);
        CarrierConfigManager configManager = (CarrierConfigManager)
                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
        int ddSubId = SubscriptionManager.getDefaultDataSubscriptionId();
        String mccMnc = SubscriptionManager.isValidSubscriptionId(ddSubId)
                ? phone.getSimOperator(ddSubId) : phone.getSimOperator();
        boolean isKeepLppProfile = false;
        if (!TextUtils.isEmpty(mccMnc)) {
            if (DEBUG) Log.d(TAG, "SIM MCC/MNC is available: " + mccMnc);
            if (configManager != null) {
                PersistableBundle b = SubscriptionManager.isValidSubscriptionId(ddSubId)
                        ? configManager.getConfigForSubId(ddSubId) : null;
                if (b != null) {
                    isKeepLppProfile =
                            b.getBoolean(CarrierConfigManager.Gps.KEY_PERSIST_LPP_MODE_BOOL);
                }
            }
            if (isKeepLppProfile) {
                // load current properties for the carrier
                mGnssConfiguration.loadPropertiesFromCarrierConfig();
                String lpp_profile = mGnssConfiguration.getLppProfile();
                // set the persist property LPP_PROFILE for the value
                if (lpp_profile != null) {
                    SystemProperties.set(GnssConfiguration.LPP_PROFILE, lpp_profile);
                }
            } else {
                // reset the persist property
                SystemProperties.set(GnssConfiguration.LPP_PROFILE, "");
            }
            reloadGpsProperties();
        } else {
            if (DEBUG) Log.d(TAG, "SIM MCC/MNC is still not available");
        }
    }

    private void updateLowPowerMode() {
        // Disable GPS if we are in device idle mode.
        boolean disableGpsForPowerManager = mPowerManager.isDeviceIdleMode();
        final PowerSaveState result =
                mPowerManager.getPowerSaveState(ServiceType.LOCATION);
        switch (result.locationMode) {
            case PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
            case PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
                // If we are in battery saver mode and the screen is off, disable GPS.
                disableGpsForPowerManager |=
                        result.batterySaverEnabled && !mPowerManager.isInteractive();
                break;
        }
        if (disableGpsForPowerManager != mDisableGpsForPowerManager) {
            mDisableGpsForPowerManager = disableGpsForPowerManager;
            updateEnabled();
            updateRequirements();
        }
    }

    public static boolean isSupported() {
        return native_is_supported();
    }

    private void reloadGpsProperties() {
        mGnssConfiguration.reloadGpsProperties();
        setSuplHostPort();
        // TODO: we should get rid of C2K specific setting.
        mC2KServerHost = mGnssConfiguration.getC2KHost();
        mC2KServerPort = mGnssConfiguration.getC2KPort(TCP_MIN_PORT);
        mNIHandler.setEmergencyExtensionSeconds(mGnssConfiguration.getEsExtensionSec());
        mSuplEsEnabled = mGnssConfiguration.getSuplEs(0) == 1;
        mNIHandler.setSuplEsEnabled(mSuplEsEnabled);
        if (mGnssVisibilityControl != null) {
            mGnssVisibilityControl.onConfigurationUpdated(mGnssConfiguration);
        }
    }

    public GnssLocationProvider(Context context, LocationProviderManager locationProviderManager,
            Looper looper) {
        super(context, locationProviderManager);

        mLooper = looper;

        // Create a wake lock
        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
        mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
        mWakeLock.setReferenceCounted(true);

        // Create a separate wake lock for psds downloader as it may be released due to timeout.
        mDownloadPsdsWakeLock = mPowerManager.newWakeLock(
                PowerManager.PARTIAL_WAKE_LOCK, DOWNLOAD_EXTRA_WAKELOCK_KEY);
        mDownloadPsdsWakeLock.setReferenceCounted(true);

        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
        mWakeupIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_WAKEUP), 0);
        mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_TIMEOUT), 0);

        mNetworkConnectivityHandler = new GnssNetworkConnectivityHandler(context,
                GnssLocationProvider.this::onNetworkAvailable, looper);

        // App ops service to keep track of who is accessing the GPS
        mAppOps = mContext.getSystemService(AppOpsManager.class);

        // Battery statistics service to be notified when GPS turns on or off
        mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
                BatteryStats.SERVICE_NAME));

        // Construct internal handler
        mHandler = new ProviderHandler(looper);

        // Load GPS configuration and register listeners in the background:
        // some operations, such as opening files and registering broadcast receivers, can take a
        // relative long time, so the ctor() is kept to create objects needed by this instance,
        // while IO initialization and registration is delegated to our internal handler
        // this approach is just fine because events are posted to our handler anyway
        mGnssConfiguration = new GnssConfiguration(mContext);
        mGnssCapabilitiesProvider = new GnssCapabilitiesProvider();
        // Create a GPS net-initiated handler (also needed by handleInitialize)
        mNIHandler = new GpsNetInitiatedHandler(context,
                mNetInitiatedListener,
                mSuplEsEnabled);
        sendMessage(INITIALIZE_HANDLER, 0, null);

        mGnssStatusListenerHelper = new GnssStatusListenerHelper(mContext, mHandler) {
            @Override
            protected boolean isAvailableInPlatform() {
                return isSupported();
            }

            @Override
            protected boolean isGpsEnabled() {
                return GnssLocationProvider.this.isGpsEnabled();
            }
        };

        mGnssMeasurementsProvider = new GnssMeasurementsProvider(mContext, mHandler) {
            @Override
            protected boolean isGpsEnabled() {
                return GnssLocationProvider.this.isGpsEnabled();
            }
        };

        mGnssMeasurementCorrectionsProvider = new GnssMeasurementCorrectionsProvider(mHandler);

        mGnssNavigationMessageProvider = new GnssNavigationMessageProvider(mContext, mHandler) {
            @Override
            protected boolean isGpsEnabled() {
                return GnssLocationProvider.this.isGpsEnabled();
            }
        };

        mGnssMetrics = new GnssMetrics(mBatteryStats);
        mNtpTimeHelper = new NtpTimeHelper(mContext, looper, this);
        GnssSatelliteBlacklistHelper gnssSatelliteBlacklistHelper =
                new GnssSatelliteBlacklistHelper(mContext,
                        looper, this);
        mHandler.post(gnssSatelliteBlacklistHelper::updateSatelliteBlacklist);
        mGnssBatchingProvider = new GnssBatchingProvider();
        mGnssGeofenceProvider = new GnssGeofenceProvider();

        mContext.registerReceiverAsUser(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (getSendingUserId() == UserHandle.USER_ALL) {
                    mShutdown = true;
                    updateEnabled();
                }
            }
        }, UserHandle.ALL, new IntentFilter(Intent.ACTION_SHUTDOWN), null, mHandler);

        mContext.getContentResolver().registerContentObserver(
                Settings.Secure.getUriFor(Settings.Secure.LOCATION_MODE),
                true,
                new ContentObserver(mHandler) {
                    @Override
                    public void onChange(boolean selfChange) {
                        updateEnabled();
                    }
                }, UserHandle.USER_ALL);

        setProperties(PROPERTIES);
        setEnabled(true);
    }

    /**
     * Implements {@link InjectNtpTimeCallback#injectTime}
     */
    @Override
    public void injectTime(long time, long timeReference, int uncertainty) {
        native_inject_time(time, timeReference, uncertainty);
    }

    /**
     * Implements {@link GnssNetworkConnectivityHandler.GnssNetworkListener#onNetworkAvailable()}
     */
    private void onNetworkAvailable() {
        mNtpTimeHelper.onNetworkAvailable();
        if (mDownloadPsdsDataPending == STATE_PENDING_NETWORK) {
            if (mSupportsPsds) {
                // Download only if supported, (prevents an unnecessary on-boot download)
                psdsDownloadRequest();
            }
        }
    }

    private void handleRequestLocation(boolean independentFromGnss, boolean isUserEmergency) {
        if (isRequestLocationRateLimited()) {
            if (DEBUG) {
                Log.d(TAG, "RequestLocation is denied due to too frequent requests.");
            }
            return;
        }
        ContentResolver resolver = mContext.getContentResolver();
        long durationMillis = Settings.Global.getLong(
                resolver,
                Settings.Global.GNSS_HAL_LOCATION_REQUEST_DURATION_MILLIS,
                LOCATION_UPDATE_DURATION_MILLIS);
        if (durationMillis == 0) {
            Log.i(TAG, "GNSS HAL location request is disabled by Settings.");
            return;
        }

        LocationManager locationManager = (LocationManager) mContext.getSystemService(
                Context.LOCATION_SERVICE);
        String provider;
        LocationChangeListener locationListener;
        LocationRequest locationRequest = new LocationRequest()
                .setInterval(LOCATION_UPDATE_MIN_TIME_INTERVAL_MILLIS)
                .setFastestInterval(LOCATION_UPDATE_MIN_TIME_INTERVAL_MILLIS);

        if (independentFromGnss) {
            // For fast GNSS TTFF
            provider = LocationManager.NETWORK_PROVIDER;
            locationListener = mNetworkLocationListener;
            locationRequest.setQuality(LocationRequest.POWER_LOW);
        } else {
            // For Device-Based Hybrid (E911)
            provider = LocationManager.FUSED_PROVIDER;
            locationListener = mFusedLocationListener;
            locationRequest.setQuality(LocationRequest.ACCURACY_FINE);
        }

        locationRequest.setProvider(provider);

        // Ignore location settings if in emergency mode.
        if (isUserEmergency && mNIHandler.getInEmergency()) {
            locationRequest.setLocationSettingsIgnored(true);
            durationMillis *= EMERGENCY_LOCATION_UPDATE_DURATION_MULTIPLIER;
        }

        Log.i(TAG,
                String.format(
                        "GNSS HAL Requesting location updates from %s provider for %d millis.",
                        provider, durationMillis));

        try {
            locationManager.requestLocationUpdates(locationRequest,
                    locationListener, mHandler.getLooper());
            locationListener.mNumLocationUpdateRequest++;
            mHandler.postDelayed(() -> {
                if (--locationListener.mNumLocationUpdateRequest == 0) {
                    Log.i(TAG,
                            String.format("Removing location updates from %s provider.", provider));
                    locationManager.removeUpdates(locationListener);
                }
            }, durationMillis);
        } catch (IllegalArgumentException e) {
            Log.w(TAG, "Unable to request location.", e);
        }
    }

    private void injectBestLocation(Location location) {
        if (DEBUG) {
            Log.d(TAG, "injectBestLocation: " + location);
        }
        int gnssLocationFlags = LOCATION_HAS_LAT_LONG |
                (location.hasAltitude() ? LOCATION_HAS_ALTITUDE : 0) |
                (location.hasSpeed() ? LOCATION_HAS_SPEED : 0) |
                (location.hasBearing() ? LOCATION_HAS_BEARING : 0) |
                (location.hasAccuracy() ? LOCATION_HAS_HORIZONTAL_ACCURACY : 0) |
                (location.hasVerticalAccuracy() ? LOCATION_HAS_VERTICAL_ACCURACY : 0) |
                (location.hasSpeedAccuracy() ? LOCATION_HAS_SPEED_ACCURACY : 0) |
                (location.hasBearingAccuracy() ? LOCATION_HAS_BEARING_ACCURACY : 0);

        double latitudeDegrees = location.getLatitude();
        double longitudeDegrees = location.getLongitude();
        double altitudeMeters = location.getAltitude();
        float speedMetersPerSec = location.getSpeed();
        float bearingDegrees = location.getBearing();
        float horizontalAccuracyMeters = location.getAccuracy();
        float verticalAccuracyMeters = location.getVerticalAccuracyMeters();
        float speedAccuracyMetersPerSecond = location.getSpeedAccuracyMetersPerSecond();
        float bearingAccuracyDegrees = location.getBearingAccuracyDegrees();
        long timestamp = location.getTime();

        int elapsedRealtimeFlags = ELAPSED_REALTIME_HAS_TIMESTAMP_NS
                | (location.hasElapsedRealtimeUncertaintyNanos()
                        ? ELAPSED_REALTIME_HAS_TIME_UNCERTAINTY_NS : 0);
        long elapsedRealtimeNanos = location.getElapsedRealtimeNanos();
        double elapsedRealtimeUncertaintyNanos = location.getElapsedRealtimeUncertaintyNanos();

        native_inject_best_location(
                gnssLocationFlags, latitudeDegrees, longitudeDegrees,
                altitudeMeters, speedMetersPerSec, bearingDegrees,
                horizontalAccuracyMeters, verticalAccuracyMeters,
                speedAccuracyMetersPerSecond, bearingAccuracyDegrees, timestamp,
                elapsedRealtimeFlags, elapsedRealtimeNanos, elapsedRealtimeUncertaintyNanos);
    }

    /** Returns true if the location request is too frequent. */
    private boolean isRequestLocationRateLimited() {
        // TODO: implement exponential backoff.
        return false;
    }

    private void handleDownloadPsdsData() {
        if (!mSupportsPsds) {
            // native code reports psds not supported, don't try
            Log.d(TAG, "handleDownloadPsdsData() called when PSDS not supported");
            return;
        }
        if (mDownloadPsdsDataPending == STATE_DOWNLOADING) {
            // already downloading data
            return;
        }
        if (!mNetworkConnectivityHandler.isDataNetworkConnected()) {
            // try again when network is up
            mDownloadPsdsDataPending = STATE_PENDING_NETWORK;
            return;
        }
        mDownloadPsdsDataPending = STATE_DOWNLOADING;

        synchronized (mLock) {
            // hold wake lock while task runs
            mDownloadPsdsWakeLock.acquire(DOWNLOAD_PSDS_DATA_TIMEOUT_MS);
        }
        Log.i(TAG, "WakeLock acquired by handleDownloadPsdsData()");
        AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
            GpsPsdsDownloader psdsDownloader = new GpsPsdsDownloader(
                    mGnssConfiguration.getProperties());
            byte[] data = psdsDownloader.downloadPsdsData();
            if (data != null) {
                if (DEBUG) Log.d(TAG, "calling native_inject_psds_data");
                native_inject_psds_data(data, data.length);
                mPsdsBackOff.reset();
            }

            sendMessage(DOWNLOAD_PSDS_DATA_FINISHED, 0, null);

            if (data == null) {
                // try again later
                // since this is delayed and not urgent we do not hold a wake lock here
                mHandler.sendEmptyMessageDelayed(DOWNLOAD_PSDS_DATA,
                        mPsdsBackOff.nextBackoffMillis());
            }

            // Release wake lock held by task, synchronize on mLock in case multiple
            // download tasks overrun.
            synchronized (mLock) {
                if (mDownloadPsdsWakeLock.isHeld()) {
                    // This wakelock may have time-out, if a timeout was specified.
                    // Catch (and ignore) any timeout exceptions.
                    try {
                        mDownloadPsdsWakeLock.release();
                        if (DEBUG) Log.d(TAG, "WakeLock released by handleDownloadPsdsData()");
                    } catch (Exception e) {
                        Log.i(TAG, "Wakelock timeout & release race exception in "
                                + "handleDownloadPsdsData()", e);
                    }
                } else {
                    Log.e(TAG, "WakeLock expired before release in "
                            + "handleDownloadPsdsData()");
                }
            }
        });
    }

    private void handleUpdateLocation(Location location) {
        if (location.hasAccuracy()) {
            if (DEBUG) {
                Log.d(TAG, "injectLocation: " + location);
            }
            native_inject_location(location.getLatitude(), location.getLongitude(),
                    location.getAccuracy());
        }
    }

    private void setSuplHostPort() {
        mSuplServerHost = mGnssConfiguration.getSuplHost();
        mSuplServerPort = mGnssConfiguration.getSuplPort(TCP_MIN_PORT);
        if (mSuplServerHost != null
                && mSuplServerPort > TCP_MIN_PORT
                && mSuplServerPort <= TCP_MAX_PORT) {
            native_set_agps_server(GnssNetworkConnectivityHandler.AGPS_TYPE_SUPL,
                    mSuplServerHost, mSuplServerPort);
        }
    }

    /**
     * Checks what SUPL mode to use, according to the AGPS mode as well as the
     * allowed mode from properties.
     *
     * @param agpsEnabled whether AGPS is enabled by settings value
     * @return SUPL mode (MSA vs MSB vs STANDALONE)
     */
    private int getSuplMode(boolean agpsEnabled) {
        if (agpsEnabled) {
            int suplMode = mGnssConfiguration.getSuplMode(0);
            if (suplMode == 0) {
                return GPS_POSITION_MODE_STANDALONE;
            }

            // MS-Based is the preferred mode for Assisted-GPS position computation, so we favor
            // such mode when it is available
            if (hasCapability(GPS_CAPABILITY_MSB) && (suplMode & AGPS_SUPL_MODE_MSB) != 0) {
                return GPS_POSITION_MODE_MS_BASED;
            }
        }
        return GPS_POSITION_MODE_STANDALONE;
    }

    private void setGpsEnabled(boolean enabled) {
        synchronized (mLock) {
            mGpsEnabled = enabled;
        }
    }

    private void handleEnable() {
        if (DEBUG) Log.d(TAG, "handleEnable");

        boolean inited = native_init();

        if (inited) {
            setGpsEnabled(true);
            mSupportsPsds = native_supports_psds();

            // TODO: remove the following native calls if we can make sure they are redundant.
            if (mSuplServerHost != null) {
                native_set_agps_server(GnssNetworkConnectivityHandler.AGPS_TYPE_SUPL,
                        mSuplServerHost, mSuplServerPort);
            }
            if (mC2KServerHost != null) {
                native_set_agps_server(GnssNetworkConnectivityHandler.AGPS_TYPE_C2K,
                        mC2KServerHost, mC2KServerPort);
            }

            mGnssMeasurementsProvider.onGpsEnabledChanged();
            mGnssNavigationMessageProvider.onGpsEnabledChanged();
            mGnssBatchingProvider.enable();
            if (mGnssVisibilityControl != null) {
                mGnssVisibilityControl.onGpsEnabledChanged(/* isEnabled= */ true);
            }
        } else {
            setGpsEnabled(false);
            Log.w(TAG, "Failed to enable location provider");
        }
    }

    private void handleDisable() {
        if (DEBUG) Log.d(TAG, "handleDisable");

        setGpsEnabled(false);
        updateClientUids(new WorkSource());
        stopNavigating();
        mAlarmManager.cancel(mWakeupIntent);
        mAlarmManager.cancel(mTimeoutIntent);

        if (mGnssVisibilityControl != null) {
            mGnssVisibilityControl.onGpsEnabledChanged(/* isEnabled= */ false);
        }
        mGnssBatchingProvider.disable();
        // do this before releasing wakelock
        native_cleanup();

        mGnssMeasurementsProvider.onGpsEnabledChanged();
        mGnssNavigationMessageProvider.onGpsEnabledChanged();
    }

    private void updateEnabled() {
        // Generally follow location setting for current user
        boolean enabled = mContext.getSystemService(LocationManager.class)
                .isLocationEnabledForUser(UserHandle.CURRENT);

        // ... but disable if PowerManager overrides
        enabled &= !mDisableGpsForPowerManager;

        // .. but enable anyway, if there's an active settings-ignored request (e.g. ELS)
        enabled |= (mProviderRequest != null && mProviderRequest.reportLocation
                        && mProviderRequest.locationSettingsIgnored);

        // ... and, finally, disable anyway, if device is being shut down
        enabled &= !mShutdown;

        if (enabled == isGpsEnabled()) {
            return;
        }

        if (enabled) {
            handleEnable();
        } else {
            handleDisable();
        }
    }

    private boolean isGpsEnabled() {
        synchronized (mLock) {
            return mGpsEnabled;
        }
    }

    @Override
    public int getStatus(Bundle extras) {
        mLocationExtras.setBundle(extras);
        return mStatus;
    }

    private void updateStatus(int status) {
        if (status != mStatus) {
            mStatus = status;
            mStatusUpdateTime = SystemClock.elapsedRealtime();
        }
    }

    @Override
    public long getStatusUpdateTime() {
        return mStatusUpdateTime;
    }

    @Override
    public void setRequest(ProviderRequest request, WorkSource source) {
        sendMessage(SET_REQUEST, 0, new GpsRequest(request, source));
    }

    private void handleSetRequest(ProviderRequest request, WorkSource source) {
        mProviderRequest = request;
        mWorkSource = source;
        updateEnabled();
        updateRequirements();
    }

    // Called when the requirements for GPS may have changed
    private void updateRequirements() {
        if (mProviderRequest == null || mWorkSource == null) {
            return;
        }

        if (DEBUG) Log.d(TAG, "setRequest " + mProviderRequest);
        if (mProviderRequest.reportLocation && isGpsEnabled()) {
            // update client uids
            updateClientUids(mWorkSource);

            mFixInterval = (int) mProviderRequest.interval;
            mLowPowerMode = mProviderRequest.lowPowerMode;
            // check for overflow
            if (mFixInterval != mProviderRequest.interval) {
                Log.w(TAG, "interval overflow: " + mProviderRequest.interval);
                mFixInterval = Integer.MAX_VALUE;
            }

            // apply request to GPS engine
            if (mStarted && hasCapability(GPS_CAPABILITY_SCHEDULING)) {
                // change period and/or lowPowerMode
                if (!setPositionMode(mPositionMode, GPS_POSITION_RECURRENCE_PERIODIC,
                        mFixInterval, 0, 0, mLowPowerMode)) {
                    Log.e(TAG, "set_position_mode failed in updateRequirements");
                }
            } else if (!mStarted) {
                // start GPS
                startNavigating();
            } else {
                // GNSS Engine is already ON, but no GPS_CAPABILITY_SCHEDULING
                mAlarmManager.cancel(mTimeoutIntent);
                if (mFixInterval >= NO_FIX_TIMEOUT) {
                    // set timer to give up if we do not receive a fix within NO_FIX_TIMEOUT
                    // and our fix interval is not short
                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                            SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT, mTimeoutIntent);
                }
            }
        } else {
            updateClientUids(new WorkSource());

            stopNavigating();
            mAlarmManager.cancel(mWakeupIntent);
            mAlarmManager.cancel(mTimeoutIntent);
        }
    }

    private boolean setPositionMode(int mode, int recurrence, int minInterval,
            int preferredAccuracy, int preferredTime, boolean lowPowerMode) {
        GnssPositionMode positionMode = new GnssPositionMode(mode, recurrence, minInterval,
                preferredAccuracy, preferredTime, lowPowerMode);
        if (mLastPositionMode != null && mLastPositionMode.equals(positionMode)) {
            return true;
        }

        boolean result = native_set_position_mode(mode, recurrence, minInterval,
                preferredAccuracy, preferredTime, lowPowerMode);
        if (result) {
            mLastPositionMode = positionMode;
        } else {
            mLastPositionMode = null;
        }
        return result;
    }

    private void updateClientUids(WorkSource source) {
        if (source.equals(mClientSource)) {
            return;
        }

        // (1) Inform BatteryStats that the list of IDs we're tracking changed.
        try {
            mBatteryStats.noteGpsChanged(mClientSource, source);
        } catch (RemoteException e) {
            Log.w(TAG, "RemoteException", e);
        }

        // (2) Inform AppOps service about the list of changes to UIDs.

        List[] diffs = WorkSource.diffChains(mClientSource, source);
        if (diffs != null) {
            List newChains = diffs[0];
            List goneChains = diffs[1];

            if (newChains != null) {
                for (WorkChain newChain : newChains) {
                    mAppOps.startOpNoThrow(AppOpsManager.OP_GPS, newChain.getAttributionUid(),
                            newChain.getAttributionTag());
                }
            }

            if (goneChains != null) {
                for (WorkChain goneChain : goneChains) {
                    mAppOps.finishOp(AppOpsManager.OP_GPS, goneChain.getAttributionUid(),
                            goneChain.getAttributionTag());
                }
            }

            mClientSource.transferWorkChains(source);
        }

        // Update the flat UIDs and names list and inform app-ops of all changes.
        WorkSource[] changes = mClientSource.setReturningDiffs(source);
        if (changes != null) {
            WorkSource newWork = changes[0];
            WorkSource goneWork = changes[1];

            // Update sources that were not previously tracked.
            if (newWork != null) {
                for (int i = 0; i < newWork.size(); i++) {
                    mAppOps.startOpNoThrow(AppOpsManager.OP_GPS,
                            newWork.get(i), newWork.getName(i));
                }
            }

            // Update sources that are no longer tracked.
            if (goneWork != null) {
                for (int i = 0; i < goneWork.size(); i++) {
                    mAppOps.finishOp(AppOpsManager.OP_GPS, goneWork.get(i), goneWork.getName(i));
                }
            }
        }
    }

    @Override
    public void sendExtraCommand(String command, Bundle extras) {

        long identity = Binder.clearCallingIdentity();
        try {
            if ("delete_aiding_data".equals(command)) {
                deleteAidingData(extras);
            } else if ("force_time_injection".equals(command)) {
                requestUtcTime();
            } else if ("force_psds_injection".equals(command)) {
                if (mSupportsPsds) {
                    psdsDownloadRequest();
                }
            } else {
                Log.w(TAG, "sendExtraCommand: unknown command " + command);
            }
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    private void deleteAidingData(Bundle extras) {
        int flags;

        if (extras == null) {
            flags = GPS_DELETE_ALL;
        } else {
            flags = 0;
            if (extras.getBoolean("ephemeris")) flags |= GPS_DELETE_EPHEMERIS;
            if (extras.getBoolean("almanac")) flags |= GPS_DELETE_ALMANAC;
            if (extras.getBoolean("position")) flags |= GPS_DELETE_POSITION;
            if (extras.getBoolean("time")) flags |= GPS_DELETE_TIME;
            if (extras.getBoolean("iono")) flags |= GPS_DELETE_IONO;
            if (extras.getBoolean("utc")) flags |= GPS_DELETE_UTC;
            if (extras.getBoolean("health")) flags |= GPS_DELETE_HEALTH;
            if (extras.getBoolean("svdir")) flags |= GPS_DELETE_SVDIR;
            if (extras.getBoolean("svsteer")) flags |= GPS_DELETE_SVSTEER;
            if (extras.getBoolean("sadata")) flags |= GPS_DELETE_SADATA;
            if (extras.getBoolean("rti")) flags |= GPS_DELETE_RTI;
            if (extras.getBoolean("celldb-info")) flags |= GPS_DELETE_CELLDB_INFO;
            if (extras.getBoolean("all")) flags |= GPS_DELETE_ALL;
        }

        if (flags != 0) {
            native_delete_aiding_data(flags);
        }
    }

    private void startNavigating() {
        if (!mStarted) {
            if (DEBUG) Log.d(TAG, "startNavigating");
            mTimeToFirstFix = 0;
            mLastFixTime = 0;
            setStarted(true);
            mPositionMode = GPS_POSITION_MODE_STANDALONE;
            // Notify about suppressed output, if speed limit was previously exceeded.
            // Elsewhere, we check again with every speed output reported.
            if (mItarSpeedLimitExceeded) {
                Log.i(TAG, "startNavigating with ITAR limit in place. Output limited  " +
                        "until slow enough speed reported.");
            }

            boolean agpsEnabled =
                    (Settings.Global.getInt(mContext.getContentResolver(),
                            Settings.Global.ASSISTED_GPS_ENABLED, 1) != 0);
            mPositionMode = getSuplMode(agpsEnabled);

            if (DEBUG) {
                String mode;

                switch (mPositionMode) {
                    case GPS_POSITION_MODE_STANDALONE:
                        mode = "standalone";
                        break;
                    case GPS_POSITION_MODE_MS_ASSISTED:
                        mode = "MS_ASSISTED";
                        break;
                    case GPS_POSITION_MODE_MS_BASED:
                        mode = "MS_BASED";
                        break;
                    default:
                        mode = "unknown";
                        break;
                }
                Log.d(TAG, "setting position_mode to " + mode);
            }

            int interval = (hasCapability(GPS_CAPABILITY_SCHEDULING) ? mFixInterval : 1000);
            mLowPowerMode = mProviderRequest.lowPowerMode;
            if (!setPositionMode(mPositionMode, GPS_POSITION_RECURRENCE_PERIODIC,
                    interval, 0, 0, mLowPowerMode)) {
                setStarted(false);
                Log.e(TAG, "set_position_mode failed in startNavigating()");
                return;
            }
            if (!native_start()) {
                setStarted(false);
                Log.e(TAG, "native_start failed in startNavigating()");
                return;
            }

            // reset SV count to zero
            updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE);
            mLocationExtras.reset();
            mFixRequestTime = SystemClock.elapsedRealtime();
            if (!hasCapability(GPS_CAPABILITY_SCHEDULING)) {
                // set timer to give up if we do not receive a fix within NO_FIX_TIMEOUT
                // and our fix interval is not short
                if (mFixInterval >= NO_FIX_TIMEOUT) {
                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                            SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT, mTimeoutIntent);
                }
            }
        }
    }

    private void stopNavigating() {
        if (DEBUG) Log.d(TAG, "stopNavigating");
        if (mStarted) {
            setStarted(false);
            native_stop();
            mLastFixTime = 0;
            // native_stop() may reset the position mode in hardware.
            mLastPositionMode = null;

            // reset SV count to zero
            updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE);
            mLocationExtras.reset();
        }
    }

    private void setStarted(boolean started) {
        if (mStarted != started) {
            mStarted = started;
            mStartedChangedElapsedRealtime = SystemClock.elapsedRealtime();
        }
    }

    private void hibernate() {
        // stop GPS until our next fix interval arrives
        stopNavigating();
        mAlarmManager.cancel(mTimeoutIntent);
        mAlarmManager.cancel(mWakeupIntent);
        long now = SystemClock.elapsedRealtime();
        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, now + mFixInterval, mWakeupIntent);
    }

    private boolean hasCapability(int capability) {
        return (mTopHalCapabilities & capability) != 0;
    }

    @NativeEntryPoint
    private void reportLocation(boolean hasLatLong, Location location) {
        sendMessage(REPORT_LOCATION, hasLatLong ? 1 : 0, location);
    }

    private void handleReportLocation(boolean hasLatLong, Location location) {
        if (location.hasSpeed()) {
            mItarSpeedLimitExceeded = location.getSpeed() > ITAR_SPEED_LIMIT_METERS_PER_SECOND;
        }

        if (mItarSpeedLimitExceeded) {
            Log.i(TAG, "Hal reported a speed in excess of ITAR limit." +
                    "  GPS/GNSS Navigation output blocked.");
            if (mStarted) {
                mGnssMetrics.logReceivedLocationStatus(false);
            }
            return;  // No output of location allowed
        }

        if (VERBOSE) Log.v(TAG, "reportLocation " + location.toString());

        location.setExtras(mLocationExtras.getBundle());

        reportLocation(location);

        if (mStarted) {
            mGnssMetrics.logReceivedLocationStatus(hasLatLong);
            if (hasLatLong) {
                if (location.hasAccuracy()) {
                    mGnssMetrics.logPositionAccuracyMeters(location.getAccuracy());
                }
                if (mTimeToFirstFix > 0) {
                    int timeBetweenFixes = (int) (SystemClock.elapsedRealtime() - mLastFixTime);
                    mGnssMetrics.logMissedReports(mFixInterval, timeBetweenFixes);
                }
            }
        } else {
            // Warn or error about long delayed GNSS engine shutdown as this generally wastes
            // power and sends location when not expected.
            long locationAfterStartedFalseMillis =
                    SystemClock.elapsedRealtime() - mStartedChangedElapsedRealtime;
            if (locationAfterStartedFalseMillis > LOCATION_OFF_DELAY_THRESHOLD_WARN_MILLIS) {
                String logMessage = "Unexpected GNSS Location report "
                        + TimeUtils.formatDuration(locationAfterStartedFalseMillis)
                        + " after location turned off";
                if (locationAfterStartedFalseMillis > LOCATION_OFF_DELAY_THRESHOLD_ERROR_MILLIS) {
                    Log.e(TAG, logMessage);
                } else {
                    Log.w(TAG, logMessage);
                }
            }
        }

        mLastFixTime = SystemClock.elapsedRealtime();
        // report time to first fix
        if (mTimeToFirstFix == 0 && hasLatLong) {
            mTimeToFirstFix = (int) (mLastFixTime - mFixRequestTime);
            if (DEBUG) Log.d(TAG, "TTFF: " + mTimeToFirstFix);
            if (mStarted) {
                mGnssMetrics.logTimeToFirstFixMilliSecs(mTimeToFirstFix);
            }

            // notify status listeners
            mGnssStatusListenerHelper.onFirstFix(mTimeToFirstFix);
        }

        if (mStarted && mStatus != LocationProvider.AVAILABLE) {
            // For devices that use framework scheduling, a timer may be set to ensure we don't
            // spend too much power searching for a location, when the requested update rate is
            // slow.
            // As we just recievied a location, we'll cancel that timer.
            if (!hasCapability(GPS_CAPABILITY_SCHEDULING) && mFixInterval < NO_FIX_TIMEOUT) {
                mAlarmManager.cancel(mTimeoutIntent);
            }

            updateStatus(LocationProvider.AVAILABLE);
        }

        if (!hasCapability(GPS_CAPABILITY_SCHEDULING) && mStarted &&
                mFixInterval > GPS_POLLING_THRESHOLD_INTERVAL) {
            if (DEBUG) Log.d(TAG, "got fix, hibernating");
            hibernate();
        }
    }

    @NativeEntryPoint
    private void reportStatus(int status) {
        if (DEBUG) Log.v(TAG, "reportStatus status: " + status);

        boolean wasNavigating = mNavigating;
        switch (status) {
            case GPS_STATUS_SESSION_BEGIN:
                mNavigating = true;
                break;
            case GPS_STATUS_SESSION_END:
                mNavigating = false;
                break;
            case GPS_STATUS_ENGINE_ON:
                break;
            case GPS_STATUS_ENGINE_OFF:
                mNavigating = false;
                break;
        }

        if (wasNavigating != mNavigating) {
            mGnssStatusListenerHelper.onStatusChanged(mNavigating);
        }
    }

    // Helper class to carry data to handler for reportSvStatus
    private static class SvStatusInfo {
        private int mSvCount;
        private int[] mSvidWithFlags;
        private float[] mCn0s;
        private float[] mSvElevations;
        private float[] mSvAzimuths;
        private float[] mSvCarrierFreqs;
    }

    @NativeEntryPoint
    private void reportSvStatus(int svCount, int[] svidWithFlags, float[] cn0s,
            float[] svElevations, float[] svAzimuths, float[] svCarrierFreqs) {
        SvStatusInfo svStatusInfo = new SvStatusInfo();
        svStatusInfo.mSvCount = svCount;
        svStatusInfo.mSvidWithFlags = svidWithFlags;
        svStatusInfo.mCn0s = cn0s;
        svStatusInfo.mSvElevations = svElevations;
        svStatusInfo.mSvAzimuths = svAzimuths;
        svStatusInfo.mSvCarrierFreqs = svCarrierFreqs;

        sendMessage(REPORT_SV_STATUS, 0, svStatusInfo);
    }

    private void handleReportSvStatus(SvStatusInfo info) {
        mGnssStatusListenerHelper.onSvStatusChanged(
                info.mSvCount,
                info.mSvidWithFlags,
                info.mCn0s,
                info.mSvElevations,
                info.mSvAzimuths,
                info.mSvCarrierFreqs);

        // Log CN0 as part of GNSS metrics
        mGnssMetrics.logCn0(info.mCn0s, info.mSvCount);

        if (VERBOSE) {
            Log.v(TAG, "SV count: " + info.mSvCount);
        }
        // Calculate number of satellites used in fix.
        int usedInFixCount = 0;
        int maxCn0 = 0;
        int meanCn0 = 0;
        for (int i = 0; i < info.mSvCount; i++) {
            if ((info.mSvidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_USED_IN_FIX) != 0) {
                ++usedInFixCount;
                if (info.mCn0s[i] > maxCn0) {
                    maxCn0 = (int) info.mCn0s[i];
                }
                meanCn0 += info.mCn0s[i];
            }
            if (VERBOSE) {
                Log.v(TAG, "svid: " + (info.mSvidWithFlags[i] >> GnssStatus.SVID_SHIFT_WIDTH) +
                        " cn0: " + info.mCn0s[i] +
                        " elev: " + info.mSvElevations[i] +
                        " azimuth: " + info.mSvAzimuths[i] +
                        " carrier frequency: " + info.mSvCarrierFreqs[i] +
                        ((info.mSvidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_HAS_EPHEMERIS_DATA) == 0
                                ? "  " : " E") +
                        ((info.mSvidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_HAS_ALMANAC_DATA) == 0
                                ? "  " : " A") +
                        ((info.mSvidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_USED_IN_FIX) == 0
                                ? "" : "U") +
                        ((info.mSvidWithFlags[i] &
                                GnssStatus.GNSS_SV_FLAGS_HAS_CARRIER_FREQUENCY) == 0
                                ? "" : "F"));
            }

            if ((info.mSvidWithFlags[i] & GnssStatus.GNSS_SV_FLAGS_USED_IN_FIX) != 0) {
                int constellationType =
                        (info.mSvidWithFlags[i] >> GnssStatus.CONSTELLATION_TYPE_SHIFT_WIDTH)
                                & GnssStatus.CONSTELLATION_TYPE_MASK;
                mGnssMetrics.logConstellationType(constellationType);
            }
        }
        if (usedInFixCount > 0) {
            meanCn0 /= usedInFixCount;
        }
        // return number of sats used in fix instead of total reported
        mLocationExtras.set(usedInFixCount, meanCn0, maxCn0);

        if (mNavigating && mStatus == LocationProvider.AVAILABLE && mLastFixTime > 0 &&
                SystemClock.elapsedRealtime() - mLastFixTime > RECENT_FIX_TIMEOUT) {
            updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE);
        }
    }

    @NativeEntryPoint
    private void reportAGpsStatus(int agpsType, int agpsStatus, byte[] suplIpAddr) {
        mNetworkConnectivityHandler.onReportAGpsStatus(agpsType, agpsStatus, suplIpAddr);
    }

    @NativeEntryPoint
    private void reportNmea(long timestamp) {
        if (!mItarSpeedLimitExceeded) {
            int length = native_read_nmea(mNmeaBuffer, mNmeaBuffer.length);
            String nmea = new String(mNmeaBuffer, 0 /* offset */, length);
            mGnssStatusListenerHelper.onNmeaReceived(timestamp, nmea);
        }
    }

    @NativeEntryPoint
    private void reportMeasurementData(GnssMeasurementsEvent event) {
        if (!mItarSpeedLimitExceeded) {
            // send to handler to allow native to return quickly
            mHandler.post(() -> mGnssMeasurementsProvider.onMeasurementsAvailable(event));
        }
    }

    @NativeEntryPoint
    private void reportNavigationMessage(GnssNavigationMessage event) {
        if (!mItarSpeedLimitExceeded) {
            // send to handler to allow native to return quickly
            mHandler.post(() -> mGnssNavigationMessageProvider.onNavigationMessageAvailable(event));
        }
    }

    @NativeEntryPoint
    private void setTopHalCapabilities(int topHalCapabilities) {
        mHandler.post(() -> {
            mTopHalCapabilities = topHalCapabilities;

            if (hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME)) {
                mNtpTimeHelper.enablePeriodicTimeInjection();
                requestUtcTime();
            }

            mGnssMeasurementsProvider.onCapabilitiesUpdated(
                    hasCapability(GPS_CAPABILITY_MEASUREMENTS));
            mGnssNavigationMessageProvider.onCapabilitiesUpdated(
                    hasCapability(GPS_CAPABILITY_NAV_MESSAGES));
            restartRequests();

            mGnssCapabilitiesProvider.setTopHalCapabilities(mTopHalCapabilities);
        });
    }

    @NativeEntryPoint
    private void setSubHalMeasurementCorrectionsCapabilities(int subHalCapabilities) {
        mHandler.post(() -> {
            if (!mGnssMeasurementCorrectionsProvider.onCapabilitiesUpdated(subHalCapabilities)) {
                return;
            }

            mGnssCapabilitiesProvider.setSubHalMeasurementCorrectionsCapabilities(
                    subHalCapabilities);
        });
    }

    private void restartRequests() {
        Log.i(TAG, "restartRequests");

        restartLocationRequest();
        mGnssMeasurementsProvider.resumeIfStarted();
        mGnssNavigationMessageProvider.resumeIfStarted();
        mGnssBatchingProvider.resumeIfStarted();
        mGnssGeofenceProvider.resumeIfStarted();
    }

    private void restartLocationRequest() {
        if (DEBUG) Log.d(TAG, "restartLocationRequest");
        setStarted(false);
        updateRequirements();
    }

    @NativeEntryPoint
    private void setGnssYearOfHardware(final int yearOfHardware) {
        // mHardwareYear is simply set here, to be read elsewhere, and is volatile for safe sync
        if (DEBUG) Log.d(TAG, "setGnssYearOfHardware called with " + yearOfHardware);
        mHardwareYear = yearOfHardware;
    }

    @NativeEntryPoint
    private void setGnssHardwareModelName(final String modelName) {
        // mHardwareModelName is simply set here, to be read elsewhere, and volatile for safe sync
        if (DEBUG) Log.d(TAG, "setGnssModelName called with " + modelName);
        mHardwareModelName = modelName;
    }

    @NativeEntryPoint
    private void reportGnssServiceDied() {
        if (DEBUG) Log.d(TAG, "reportGnssServiceDied");
        mHandler.post(() -> {
            setupNativeGnssService(/* reinitializeGnssServiceHandle = */ true);
            if (isGpsEnabled()) {
                setGpsEnabled(false);

                updateEnabled();

                // resend configuration into the restarted HAL service.
                reloadGpsProperties();
            }
        });
    }

    public interface GnssSystemInfoProvider {
        /**
         * Returns the year of underlying GPS hardware.
         */
        int getGnssYearOfHardware();

        /**
         * Returns the model name of underlying GPS hardware.
         */
        String getGnssHardwareModelName();
    }

    /**
     * @hide
     */
    public GnssSystemInfoProvider getGnssSystemInfoProvider() {
        return new GnssSystemInfoProvider() {
            @Override
            public int getGnssYearOfHardware() {
                return mHardwareYear;
            }

            @Override
            public String getGnssHardwareModelName() {
                return mHardwareModelName;
            }
        };
    }

    /**
     * @hide
     */
    public GnssBatchingProvider getGnssBatchingProvider() {
        return mGnssBatchingProvider;
    }

    public interface GnssMetricsProvider {
        /**
         * Returns GNSS metrics as proto string
         */
        String getGnssMetricsAsProtoString();
    }

    /**
     * @hide
     */
    public GnssMetricsProvider getGnssMetricsProvider() {
        return () -> mGnssMetrics.dumpGnssMetricsAsProtoString();
    }

    /**
     * @hide
     */
    public GnssCapabilitiesProvider getGnssCapabilitiesProvider() {
        return mGnssCapabilitiesProvider;
    }

    @NativeEntryPoint
    private void reportLocationBatch(Location[] locationArray) {
        List locations = new ArrayList<>(Arrays.asList(locationArray));
        if (DEBUG) {
            Log.d(TAG, "Location batch of size " + locationArray.length + " reported");
        }
        reportLocation(locations);
    }

    @NativeEntryPoint
    private void psdsDownloadRequest() {
        if (DEBUG) Log.d(TAG, "psdsDownloadRequest");
        sendMessage(DOWNLOAD_PSDS_DATA, 0, null);
    }

    /**
     * Converts the GPS HAL status to the internal Geofence Hardware status.
     */
    private static int getGeofenceStatus(int status) {
        switch (status) {
            case GPS_GEOFENCE_OPERATION_SUCCESS:
                return GeofenceHardware.GEOFENCE_SUCCESS;
            case GPS_GEOFENCE_ERROR_GENERIC:
                return GeofenceHardware.GEOFENCE_FAILURE;
            case GPS_GEOFENCE_ERROR_ID_EXISTS:
                return GeofenceHardware.GEOFENCE_ERROR_ID_EXISTS;
            case GPS_GEOFENCE_ERROR_INVALID_TRANSITION:
                return GeofenceHardware.GEOFENCE_ERROR_INVALID_TRANSITION;
            case GPS_GEOFENCE_ERROR_TOO_MANY_GEOFENCES:
                return GeofenceHardware.GEOFENCE_ERROR_TOO_MANY_GEOFENCES;
            case GPS_GEOFENCE_ERROR_ID_UNKNOWN:
                return GeofenceHardware.GEOFENCE_ERROR_ID_UNKNOWN;
            default:
                return -1;
        }
    }

    @NativeEntryPoint
    private void reportGeofenceTransition(int geofenceId, Location location, int transition,
            long transitionTimestamp) {
        mHandler.post(() -> {
            if (mGeofenceHardwareImpl == null) {
                mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
            }

            mGeofenceHardwareImpl.reportGeofenceTransition(
                    geofenceId,
                    location,
                    transition,
                    transitionTimestamp,
                    GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE,
                    FusedBatchOptions.SourceTechnologies.GNSS);
        });
    }

    @NativeEntryPoint
    private void reportGeofenceStatus(int status, Location location) {
        mHandler.post(() -> {
            if (mGeofenceHardwareImpl == null) {
                mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
            }
            int monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_UNAVAILABLE;
            if (status == GPS_GEOFENCE_AVAILABLE) {
                monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE;
            }
            mGeofenceHardwareImpl.reportGeofenceMonitorStatus(
                    GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE,
                    monitorStatus,
                    location,
                    FusedBatchOptions.SourceTechnologies.GNSS);
        });
    }

    @NativeEntryPoint
    private void reportGeofenceAddStatus(int geofenceId, int status) {
        mHandler.post(() -> {
            if (mGeofenceHardwareImpl == null) {
                mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
            }
            mGeofenceHardwareImpl.reportGeofenceAddStatus(geofenceId, getGeofenceStatus(status));
        });
    }

    @NativeEntryPoint
    private void reportGeofenceRemoveStatus(int geofenceId, int status) {
        mHandler.post(() -> {
            if (mGeofenceHardwareImpl == null) {
                mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
            }
            mGeofenceHardwareImpl.reportGeofenceRemoveStatus(geofenceId, getGeofenceStatus(status));
        });
    }

    @NativeEntryPoint
    private void reportGeofencePauseStatus(int geofenceId, int status) {
        mHandler.post(() -> {
            if (mGeofenceHardwareImpl == null) {
                mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
            }
            mGeofenceHardwareImpl.reportGeofencePauseStatus(geofenceId, getGeofenceStatus(status));
        });
    }

    @NativeEntryPoint
    private void reportGeofenceResumeStatus(int geofenceId, int status) {
        mHandler.post(() -> {
            if (mGeofenceHardwareImpl == null) {
                mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
            }
            mGeofenceHardwareImpl.reportGeofenceResumeStatus(geofenceId, getGeofenceStatus(status));
        });
    }

    //=============================================================
    // NI Client support
    //=============================================================
    private final INetInitiatedListener mNetInitiatedListener = new INetInitiatedListener.Stub() {
        // Sends a response for an NI request to HAL.
        @Override
        public boolean sendNiResponse(int notificationId, int userResponse) {
            // TODO Add Permission check

            if (DEBUG) {
                Log.d(TAG, "sendNiResponse, notifId: " + notificationId +
                        ", response: " + userResponse);
            }
            native_send_ni_response(notificationId, userResponse);

            StatsLog.write(StatsLog.GNSS_NI_EVENT_REPORTED,
                    StatsLog.GNSS_NI_EVENT_REPORTED__EVENT_TYPE__NI_RESPONSE,
                    notificationId,
                    /* niType= */ 0,
                    /* needNotify= */ false,
                    /* needVerify= */ false,
                    /* privacyOverride= */ false,
                    /* timeout= */ 0,
                    /* defaultResponse= */ 0,
                    /* requestorId= */ null,
                    /* text= */ null,
                    /* requestorIdEncoding= */ 0,
                    /* textEncoding= */ 0,
                    mSuplEsEnabled,
                    isGpsEnabled(),
                    userResponse);

            return true;
        }
    };

    public INetInitiatedListener getNetInitiatedListener() {
        return mNetInitiatedListener;
    }

    /** Reports a NI notification. */
    @NativeEntryPoint
    public void reportNiNotification(
            int notificationId,
            int niType,
            int notifyFlags,
            int timeout,
            int defaultResponse,
            String requestorId,
            String text,
            int requestorIdEncoding,
            int textEncoding
    ) {
        Log.i(TAG, "reportNiNotification: entered");
        Log.i(TAG, "notificationId: " + notificationId +
                ", niType: " + niType +
                ", notifyFlags: " + notifyFlags +
                ", timeout: " + timeout +
                ", defaultResponse: " + defaultResponse);

        Log.i(TAG, "requestorId: " + requestorId +
                ", text: " + text +
                ", requestorIdEncoding: " + requestorIdEncoding +
                ", textEncoding: " + textEncoding);

        GpsNiNotification notification = new GpsNiNotification();

        notification.notificationId = notificationId;
        notification.niType = niType;
        notification.needNotify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_NOTIFY) != 0;
        notification.needVerify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_VERIFY) != 0;
        notification.privacyOverride =
                (notifyFlags & GpsNetInitiatedHandler.GPS_NI_PRIVACY_OVERRIDE) != 0;
        notification.timeout = timeout;
        notification.defaultResponse = defaultResponse;
        notification.requestorId = requestorId;
        notification.text = text;
        notification.requestorIdEncoding = requestorIdEncoding;
        notification.textEncoding = textEncoding;

        mNIHandler.handleNiNotification(notification);
        StatsLog.write(StatsLog.GNSS_NI_EVENT_REPORTED,
                StatsLog.GNSS_NI_EVENT_REPORTED__EVENT_TYPE__NI_REQUEST,
                notification.notificationId,
                notification.niType,
                notification.needNotify,
                notification.needVerify,
                notification.privacyOverride,
                notification.timeout,
                notification.defaultResponse,
                notification.requestorId,
                notification.text,
                notification.requestorIdEncoding,
                notification.textEncoding,
                mSuplEsEnabled,
                isGpsEnabled(),
                /* userResponse= */ 0);
    }

    /**
     * We should be careful about receiving null string from the TelephonyManager,
     * because sending null String to JNI function would cause a crash.
     */
    @NativeEntryPoint
    private void requestSetID(int flags) {
        TelephonyManager phone = (TelephonyManager)
                mContext.getSystemService(Context.TELEPHONY_SERVICE);
        int type = AGPS_SETID_TYPE_NONE;
        String setId = null;

        int ddSubId = SubscriptionManager.getDefaultDataSubscriptionId();
        if ((flags & AGPS_RIL_REQUEST_SETID_IMSI) == AGPS_RIL_REQUEST_SETID_IMSI) {
            if (SubscriptionManager.isValidSubscriptionId(ddSubId)) {
                setId = phone.getSubscriberId(ddSubId);
            }
            if (setId == null) {
                setId = phone.getSubscriberId();
            }
            if (setId != null) {
                // This means the framework has the SIM card.
                type = AGPS_SETID_TYPE_IMSI;
            }
        } else if ((flags & AGPS_RIL_REQUEST_SETID_MSISDN) == AGPS_RIL_REQUEST_SETID_MSISDN) {
            if (SubscriptionManager.isValidSubscriptionId(ddSubId)) {
                setId = phone.getLine1Number(ddSubId);
            }
            if (setId == null) {
                setId = phone.getLine1Number();
            }
            if (setId != null) {
                // This means the framework has the SIM card.
                type = AGPS_SETID_TYPE_MSISDN;
            }
        }

        native_agps_set_id(type, (setId == null) ? "" : setId);
    }

    @NativeEntryPoint
    private void requestLocation(boolean independentFromGnss, boolean isUserEmergency) {
        if (DEBUG) {
            Log.d(TAG, "requestLocation. independentFromGnss: " + independentFromGnss
                    + ", isUserEmergency: "
                    + isUserEmergency);
        }
        sendMessage(REQUEST_LOCATION, independentFromGnss ? 1 : 0, isUserEmergency);
    }

    @NativeEntryPoint
    private void requestUtcTime() {
        if (DEBUG) Log.d(TAG, "utcTimeRequest");
        sendMessage(INJECT_NTP_TIME, 0, null);
    }

    @NativeEntryPoint
    private void requestRefLocation() {
        TelephonyManager phone = (TelephonyManager)
                mContext.getSystemService(Context.TELEPHONY_SERVICE);
        final int phoneType = phone.getPhoneType();
        if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
            GsmCellLocation gsm_cell = (GsmCellLocation) phone.getCellLocation();
            if ((gsm_cell != null) && (phone.getNetworkOperator() != null)
                    && (phone.getNetworkOperator().length() > 3)) {
                int type;
                int mcc = Integer.parseInt(phone.getNetworkOperator().substring(0, 3));
                int mnc = Integer.parseInt(phone.getNetworkOperator().substring(3));
                int networkType = phone.getNetworkType();
                if (networkType == TelephonyManager.NETWORK_TYPE_UMTS
                        || networkType == TelephonyManager.NETWORK_TYPE_HSDPA
                        || networkType == TelephonyManager.NETWORK_TYPE_HSUPA
                        || networkType == TelephonyManager.NETWORK_TYPE_HSPA
                        || networkType == TelephonyManager.NETWORK_TYPE_HSPAP) {
                    type = AGPS_REF_LOCATION_TYPE_UMTS_CELLID;
                } else {
                    type = AGPS_REF_LOCATION_TYPE_GSM_CELLID;
                }
                native_agps_set_ref_location_cellid(type, mcc, mnc,
                        gsm_cell.getLac(), gsm_cell.getCid());
            } else {
                Log.e(TAG, "Error getting cell location info.");
            }
        } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
            Log.e(TAG, "CDMA not supported.");
        }
    }

    // Implements method nfwNotifyCb() in IGnssVisibilityControlCallback.hal.
    @NativeEntryPoint
    private void reportNfwNotification(String proxyAppPackageName, byte protocolStack,
            String otherProtocolStackName, byte requestor, String requestorId, byte responseType,
            boolean inEmergencyMode, boolean isCachedLocation) {
        if (mGnssVisibilityControl == null) {
            Log.e(TAG, "reportNfwNotification: mGnssVisibilityControl is not initialized.");
            return;
        }

        mGnssVisibilityControl.reportNfwNotification(proxyAppPackageName, protocolStack,
                otherProtocolStackName, requestor, requestorId, responseType, inEmergencyMode,
                isCachedLocation);
    }

    // Implements method isInEmergencySession() in IGnssVisibilityControlCallback.hal.
    @NativeEntryPoint
    boolean isInEmergencySession() {
        return mNIHandler.getInEmergency();
    }

    private void sendMessage(int message, int arg, Object obj) {
        // hold a wake lock until this message is delivered
        // note that this assumes the message will not be removed from the queue before
        // it is handled (otherwise the wake lock would be leaked).
        mWakeLock.acquire();
        if (DEBUG) {
            Log.d(TAG, "WakeLock acquired by sendMessage(" + messageIdAsString(message) + ", " + arg
                    + ", " + obj + ")");
        }
        mHandler.obtainMessage(message, arg, 1, obj).sendToTarget();
    }

    private final class ProviderHandler extends Handler {
        public ProviderHandler(Looper looper) {
            super(looper, null, true /*async*/);
        }

        @Override
        public void handleMessage(Message msg) {
            int message = msg.what;
            switch (message) {
                case SET_REQUEST:
                    GpsRequest gpsRequest = (GpsRequest) msg.obj;
                    handleSetRequest(gpsRequest.request, gpsRequest.source);
                    break;
                case INJECT_NTP_TIME:
                    mNtpTimeHelper.retrieveAndInjectNtpTime();
                    break;
                case REQUEST_LOCATION:
                    handleRequestLocation(msg.arg1 == 1, (boolean) msg.obj);
                    break;
                case DOWNLOAD_PSDS_DATA:
                    handleDownloadPsdsData();
                    break;
                case DOWNLOAD_PSDS_DATA_FINISHED:
                    mDownloadPsdsDataPending = STATE_IDLE;
                    break;
                case UPDATE_LOCATION:
                    handleUpdateLocation((Location) msg.obj);
                    break;
                case INITIALIZE_HANDLER:
                    handleInitialize();
                    break;
                case REPORT_LOCATION:
                    handleReportLocation(msg.arg1 == 1, (Location) msg.obj);
                    break;
                case REPORT_SV_STATUS:
                    handleReportSvStatus((SvStatusInfo) msg.obj);
                    break;
            }
            if (msg.arg2 == 1) {
                // wakelock was taken for this message, release it
                mWakeLock.release();
                if (DEBUG) {
                    Log.d(TAG, "WakeLock released by handleMessage(" + messageIdAsString(message)
                            + ", " + msg.arg1 + ", " + msg.obj + ")");
                }
            }
        }

        /**
         * This method is bound to {@link #GnssLocationProvider(Context, LocationProviderManager,
         * Looper)}.
         * It is in charge of loading properties and registering for events that will be posted to
         * this handler.
         */
        private void handleInitialize() {
            // class_init_native() already initializes the GNSS service handle during class loading.
            setupNativeGnssService(/* reinitializeGnssServiceHandle = */ false);

            if (native_is_gnss_visibility_control_supported()) {
                mGnssVisibilityControl = new GnssVisibilityControl(mContext, mLooper, mNIHandler);
            }

            // load default GPS configuration
            // (this configuration might change in the future based on SIM changes)
            reloadGpsProperties();

            // listen for events
            IntentFilter intentFilter = new IntentFilter();
            intentFilter.addAction(ALARM_WAKEUP);
            intentFilter.addAction(ALARM_TIMEOUT);
            intentFilter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
            intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
            intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
            intentFilter.addAction(Intent.ACTION_SCREEN_ON);
            intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
            intentFilter.addAction(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
            mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, this);

            mNetworkConnectivityHandler.registerNetworkCallbacks();

            // listen for PASSIVE_PROVIDER updates
            LocationManager locManager =
                    (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
            long minTime = 0;
            float minDistance = 0;
            LocationRequest request = LocationRequest.createFromDeprecatedProvider(
                    LocationManager.PASSIVE_PROVIDER,
                    minTime,
                    minDistance,
                    false);
            // Don't keep track of this request since it's done on behalf of other clients
            // (which are kept track of separately).
            request.setHideFromAppOps(true);
            locManager.requestLocationUpdates(
                    request,
                    new NetworkLocationListener(),
                    getLooper());

            updateEnabled();
        }
    }

    private abstract class LocationChangeListener implements LocationListener {
        private int mNumLocationUpdateRequest;

        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) {
        }

        @Override
        public void onProviderEnabled(String provider) {
        }

        @Override
        public void onProviderDisabled(String provider) {
        }
    }

    private final class NetworkLocationListener extends LocationChangeListener {
        @Override
        public void onLocationChanged(Location location) {
            // this callback happens on mHandler looper
            if (LocationManager.NETWORK_PROVIDER.equals(location.getProvider())) {
                handleUpdateLocation(location);
            }
        }
    }

    private final class FusedLocationListener extends LocationChangeListener {
        @Override
        public void onLocationChanged(Location location) {
            if (LocationManager.FUSED_PROVIDER.equals(location.getProvider())) {
                injectBestLocation(location);
            }
        }
    }

    /**
     * @return A string representing the given message ID.
     */
    private String messageIdAsString(int message) {
        switch (message) {
            case SET_REQUEST:
                return "SET_REQUEST";
            case INJECT_NTP_TIME:
                return "INJECT_NTP_TIME";
            case REQUEST_LOCATION:
                return "REQUEST_LOCATION";
            case DOWNLOAD_PSDS_DATA:
                return "DOWNLOAD_PSDS_DATA";
            case DOWNLOAD_PSDS_DATA_FINISHED:
                return "DOWNLOAD_PSDS_DATA_FINISHED";
            case UPDATE_LOCATION:
                return "UPDATE_LOCATION";
            case INITIALIZE_HANDLER:
                return "INITIALIZE_HANDLER";
            case REPORT_LOCATION:
                return "REPORT_LOCATION";
            case REPORT_SV_STATUS:
                return "REPORT_SV_STATUS";
            default:
                return "";
        }
    }

    @Override
    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        StringBuilder s = new StringBuilder();
        s.append("  mStarted=").append(mStarted).append("   (changed ");
        TimeUtils.formatDuration(SystemClock.elapsedRealtime()
                - mStartedChangedElapsedRealtime, s);
        s.append(" ago)").append('\n');
        s.append("  mFixInterval=").append(mFixInterval).append('\n');
        s.append("  mLowPowerMode=").append(mLowPowerMode).append('\n');
        s.append("  mGnssMeasurementsProvider.isRegistered()=")
                .append(mGnssMeasurementsProvider.isRegistered()).append('\n');
        s.append("  mGnssNavigationMessageProvider.isRegistered()=")
                .append(mGnssNavigationMessageProvider.isRegistered()).append('\n');
        s.append("  mDisableGpsForPowerManager=").append(mDisableGpsForPowerManager).append('\n');
        s.append("  mTopHalCapabilities=0x").append(Integer.toHexString(mTopHalCapabilities));
        s.append(" ( ");
        if (hasCapability(GPS_CAPABILITY_SCHEDULING)) s.append("SCHEDULING ");
        if (hasCapability(GPS_CAPABILITY_MSB)) s.append("MSB ");
        if (hasCapability(GPS_CAPABILITY_MSA)) s.append("MSA ");
        if (hasCapability(GPS_CAPABILITY_SINGLE_SHOT)) s.append("SINGLE_SHOT ");
        if (hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME)) s.append("ON_DEMAND_TIME ");
        if (hasCapability(GPS_CAPABILITY_GEOFENCING)) s.append("GEOFENCING ");
        if (hasCapability(GPS_CAPABILITY_MEASUREMENTS)) s.append("MEASUREMENTS ");
        if (hasCapability(GPS_CAPABILITY_NAV_MESSAGES)) s.append("NAV_MESSAGES ");
        if (hasCapability(GPS_CAPABILITY_LOW_POWER_MODE)) s.append("LOW_POWER_MODE ");
        if (hasCapability(GPS_CAPABILITY_SATELLITE_BLACKLIST)) s.append("SATELLITE_BLACKLIST ");
        if (hasCapability(GPS_CAPABILITY_MEASUREMENT_CORRECTIONS)) {
            s.append("MEASUREMENT_CORRECTIONS ");
        }
        s.append(")\n");
        if (hasCapability(GPS_CAPABILITY_MEASUREMENT_CORRECTIONS)) {
            s.append("  SubHal=MEASUREMENT_CORRECTIONS[");
            s.append(mGnssMeasurementCorrectionsProvider.toStringCapabilities());
            s.append("]\n");
        }
        s.append(mGnssMetrics.dumpGnssMetricsAsText());
        s.append("  native internal state: ").append(native_get_internal_state());
        s.append("\n");
        pw.append(s);
    }

    private void setupNativeGnssService(boolean reinitializeGnssServiceHandle) {
        native_init_once(reinitializeGnssServiceHandle);

        /*
         * A cycle of native_init() and native_cleanup() is needed so that callbacks are
         * registered after bootup even when location is disabled.
         * This will allow Emergency SUPL to work even when location is disabled before device
         * restart.
         */
        boolean isInitialized = native_init();
        if (!isInitialized) {
            Log.w(TAG, "Native initialization failed.");
        } else {
            native_cleanup();
        }
    }

    // preallocated to avoid memory allocation in reportNmea()
    private byte[] mNmeaBuffer = new byte[120];

    static {
        class_init_native();
    }

    private static native void class_init_native();

    private static native boolean native_is_supported();

    private static native boolean native_is_gnss_visibility_control_supported();

    private static native void native_init_once(boolean reinitializeGnssServiceHandle);

    private native boolean native_init();

    private native void native_cleanup();

    private native boolean native_set_position_mode(int mode, int recurrence, int min_interval,
            int preferred_accuracy, int preferred_time, boolean lowPowerMode);

    private native boolean native_start();

    private native boolean native_stop();

    private native void native_delete_aiding_data(int flags);

    private native int native_read_nmea(byte[] buffer, int bufferSize);

    private native void native_inject_best_location(
            int gnssLocationFlags, double latitudeDegrees, double longitudeDegrees,
            double altitudeMeters, float speedMetersPerSec, float bearingDegrees,
            float horizontalAccuracyMeters, float verticalAccuracyMeters,
            float speedAccuracyMetersPerSecond, float bearingAccuracyDegrees,
            long timestamp, int elapsedRealtimeFlags, long elapsedRealtimeNanos,
            double elapsedRealtimeUncertaintyNanos);

    private native void native_inject_location(double latitude, double longitude, float accuracy);

    // PSDS Support
    private native void native_inject_time(long time, long timeReference, int uncertainty);

    private native boolean native_supports_psds();

    private native void native_inject_psds_data(byte[] data, int length);

    // DEBUG Support
    private native String native_get_internal_state();

    // AGPS Support
    private native void native_agps_ni_message(byte[] msg, int length);

    private native void native_set_agps_server(int type, String hostname, int port);

    // Network-initiated (NI) Support
    private native void native_send_ni_response(int notificationId, int userResponse);

    // AGPS ril support
    private native void native_agps_set_ref_location_cellid(int type, int mcc, int mnc,
            int lac, int cid);

    private native void native_agps_set_id(int type, String setid);
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy