blob: 9956da274263627a1d129617c087baab593ec862 [file] [log] [blame]
/*
* Copyright (C) 2020 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.gnss;
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
import static android.location.provider.ProviderProperties.ACCURACY_FINE;
import static android.location.provider.ProviderProperties.POWER_USAGE_HIGH;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.server.location.gnss.hal.GnssNative.AGPS_REF_LOCATION_TYPE_GSM_CELLID;
import static com.android.server.location.gnss.hal.GnssNative.AGPS_REF_LOCATION_TYPE_UMTS_CELLID;
import static com.android.server.location.gnss.hal.GnssNative.AGPS_SETID_TYPE_IMSI;
import static com.android.server.location.gnss.hal.GnssNative.AGPS_SETID_TYPE_MSISDN;
import static com.android.server.location.gnss.hal.GnssNative.AGPS_SETID_TYPE_NONE;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_AIDING_TYPE_ALL;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_AIDING_TYPE_ALMANAC;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_AIDING_TYPE_CELLDB_INFO;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_AIDING_TYPE_EPHEMERIS;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_AIDING_TYPE_HEALTH;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_AIDING_TYPE_IONO;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_AIDING_TYPE_POSITION;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_AIDING_TYPE_RTI;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_AIDING_TYPE_SADATA;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_AIDING_TYPE_SVDIR;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_AIDING_TYPE_SVSTEER;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_AIDING_TYPE_TIME;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_AIDING_TYPE_UTC;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_POSITION_MODE_MS_ASSISTED;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_POSITION_MODE_MS_BASED;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_POSITION_MODE_STANDALONE;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_POSITION_RECURRENCE_PERIODIC;
import static java.lang.Math.abs;
import static java.lang.Math.max;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.app.AlarmManager;
import android.app.AppOpsManager;
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.location.GnssCapabilities;
import android.location.GnssStatus;
import android.location.INetInitiatedListener;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationRequest;
import android.location.LocationResult;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
import android.location.util.identity.CallerIdentity;
import android.os.AsyncTask;
import android.os.BatteryStats;
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.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.text.format.DateUtils;
import android.util.Log;
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.util.FrameworkStatsLog;
import com.android.server.FgThread;
import com.android.server.location.gnss.GnssSatelliteBlocklistHelper.GnssSatelliteBlocklistCallback;
import com.android.server.location.gnss.NtpTimeHelper.InjectNtpTimeCallback;
import com.android.server.location.gnss.hal.GnssNative;
import com.android.server.location.injector.Injector;
import com.android.server.location.provider.AbstractLocationProvider;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
* A GNSS implementation of LocationProvider used by LocationManager.
*
* {@hide}
*/
public class GnssLocationProvider extends AbstractLocationProvider implements
InjectNtpTimeCallback, GnssSatelliteBlocklistCallback, GnssNative.BaseCallbacks,
GnssNative.LocationCallbacks, GnssNative.SvStatusCallbacks, GnssNative.AGpsCallbacks,
GnssNative.PsdsCallbacks, GnssNative.NotificationCallbacks,
GnssNative.LocationRequestCallbacks, GnssNative.TimeCallbacks {
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.Builder()
.setHasSatelliteRequirement(true)
.setHasAltitudeSupport(true)
.setHasSpeedSupport(true)
.setHasBearingSupport(true)
.setPowerUsage(POWER_USAGE_HIGH)
.setAccuracy(ACCURACY_FINE)
.build();
// The AGPS SUPL mode
private static final int AGPS_SUPL_MODE_MSA = 0x02;
private static final int AGPS_SUPL_MODE_MSB = 0x01;
// handler messages
private static final int INJECT_NTP_TIME = 5;
private static final int DOWNLOAD_PSDS_DATA = 6;
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
// 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;
// maximum length gnss batching may go for (1 day)
private static final int MIN_BATCH_INTERVAL_MS = (int) DateUtils.SECOND_IN_MILLIS;
private static final long MAX_BATCH_LENGTH_MS = DateUtils.DAY_IN_MILLIS;
private static final long MAX_BATCH_TIMESTAMP_DELTA_MS = 500;
// 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;
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);
}
}
}
// 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 valid.
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 static final long WAKELOCK_TIMEOUT_MILLIS = 30 * 1000;
// 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;
private final Object mLock = new Object();
private final Context mContext;
private final Handler mHandler;
private final GnssNative mGnssNative;
@GuardedBy("mLock")
private final ExponentialBackOff mPsdsBackOff = new ExponentialBackOff(RETRY_INTERVAL,
MAX_RETRY_INTERVAL);
// True if we are enabled
@GuardedBy("mLock")
private boolean mGpsEnabled;
@GuardedBy("mLock")
private boolean mBatchingEnabled;
private boolean mShutdown;
private boolean mStarted;
private boolean mBatchingStarted;
private AlarmManager.OnAlarmListener mBatchingAlarm;
private long mStartedChangedElapsedRealtime;
private int mFixInterval = 1000;
private ProviderRequest mProviderRequest;
private int mPositionMode;
private GnssPositionMode mLastPositionMode;
// 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 final WorkSource mClientSource = new WorkSource();
// true if PSDS is supported
private boolean mSupportsPsds;
@GuardedBy("mLock")
private final PowerManager.WakeLock mDownloadPsdsWakeLock;
@GuardedBy("mLock")
private final Set<Integer> mPendingDownloadPsdsTypes = new HashSet<>();
/**
* Properties loaded from PROPERTIES_FILE.
* It must be accessed only inside {@link #mHandler}.
*/
private final GnssConfiguration mGnssConfiguration;
private String mSuplServerHost;
private int mSuplServerPort = TCP_MIN_PORT;
private String mC2KServerHost;
private int mC2KServerPort;
private boolean mSuplEsEnabled = false;
private final LocationExtras mLocationExtras = new LocationExtras();
private final NtpTimeHelper mNtpTimeHelper;
private final GnssSatelliteBlocklistHelper mGnssSatelliteBlocklistHelper;
// Available only on GNSS HAL 2.0 implementations and later.
private GnssVisibilityControl mGnssVisibilityControl;
private final GnssNetworkConnectivityHandler mNetworkConnectivityHandler;
private final GpsNetInitiatedHandler mNIHandler;
// Wakelocks
private final PowerManager.WakeLock mWakeLock;
private final AlarmManager mAlarmManager;
private final AlarmManager.OnAlarmListener mWakeupListener = this::startNavigating;
private final AlarmManager.OnAlarmListener mTimeoutListener = this::hibernate;
private final AppOpsManager mAppOps;
private final IBatteryStats mBatteryStats;
@GuardedBy("mLock")
private final ArrayList<Runnable> mFlushListeners = new ArrayList<>(0);
// GNSS Metrics
private final GnssMetrics mGnssMetrics;
/**
* Implements {@link GnssSatelliteBlocklistCallback#onUpdateSatelliteBlocklist}.
*/
@Override
public void onUpdateSatelliteBlocklist(int[] constellations, int[] svids) {
mHandler.post(() -> mGnssConfiguration.setSatelliteBlocklist(constellations, svids));
mGnssMetrics.resetConstellationTypes();
}
private void subscriptionOrCarrierConfigChanged() {
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();
if (SubscriptionManager.isValidSubscriptionId(ddSubId)) {
phone = phone.createForSubscriptionId(ddSubId);
}
String mccMnc = 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");
// Reload gnss config for no SIM case
mGnssConfiguration.reloadGpsProperties();
}
}
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, Injector injector, GnssNative gnssNative,
GnssMetrics gnssMetrics) {
super(FgThread.getExecutor(), CallerIdentity.fromContext(context), PROPERTIES,
Collections.emptySet());
mContext = context;
mGnssNative = gnssNative;
mGnssMetrics = gnssMetrics;
// Create a wake lock
PowerManager powerManager = Objects.requireNonNull(
mContext.getSystemService(PowerManager.class));
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*location*:" + TAG);
mWakeLock.setReferenceCounted(true);
// Create a separate wake lock for psds downloader as it may be released due to timeout.
mDownloadPsdsWakeLock = powerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, "*location*:PsdsDownload");
mDownloadPsdsWakeLock.setReferenceCounted(true);
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
// 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(FgThread.getHandler().getLooper());
// 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 = mGnssNative.getConfiguration();
// Create a GPS net-initiated handler (also needed by handleInitialize)
mNIHandler = new GpsNetInitiatedHandler(context,
mNetInitiatedListener,
mSuplEsEnabled);
// Trigger PSDS data download when the network comes up after booting.
mPendingDownloadPsdsTypes.add(GnssPsdsDownloader.LONG_TERM_PSDS_SERVER_INDEX);
mNetworkConnectivityHandler = new GnssNetworkConnectivityHandler(context,
GnssLocationProvider.this::onNetworkAvailable, mHandler.getLooper(), mNIHandler);
mNtpTimeHelper = new NtpTimeHelper(mContext, mHandler.getLooper(), this);
mGnssSatelliteBlocklistHelper =
new GnssSatelliteBlocklistHelper(mContext,
mHandler.getLooper(), this);
setAllowed(true);
mGnssNative.addBaseCallbacks(this);
mGnssNative.addLocationCallbacks(this);
mGnssNative.addSvStatusCallbacks(this);
mGnssNative.setAGpsCallbacks(this);
mGnssNative.setPsdsCallbacks(this);
mGnssNative.setNotificationCallbacks(this);
mGnssNative.setLocationRequestCallbacks(this);
mGnssNative.setTimeCallbacks(this);
}
/** Called when system is ready. */
public synchronized void onSystemReady() {
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);
mHandler.post(this::handleInitialize);
mHandler.post(mGnssSatelliteBlocklistHelper::updateSatelliteBlocklist);
}
private void handleInitialize() {
if (mGnssNative.isGnssVisibilityControlSupported()) {
mGnssVisibilityControl = new GnssVisibilityControl(mContext, mHandler.getLooper(),
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(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
intentFilter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
mContext.registerReceiver(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 CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
case TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
subscriptionOrCarrierConfigChanged();
break;
}
}
}, intentFilter, null, mHandler);
mNetworkConnectivityHandler.registerNetworkCallbacks();
// permanently passively listen to all network locations
LocationManager locationManager = Objects.requireNonNull(
mContext.getSystemService(LocationManager.class));
if (locationManager.getAllProviders().contains(LocationManager.NETWORK_PROVIDER)) {
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
new LocationRequest.Builder(LocationRequest.PASSIVE_INTERVAL)
.setMinUpdateIntervalMillis(0)
.setHiddenFromAppOps(true)
.build(),
DIRECT_EXECUTOR,
this::injectLocation);
}
updateEnabled();
}
/**
* Implements {@link InjectNtpTimeCallback#injectTime}
*/
@Override
public void injectTime(long time, long timeReference, int uncertainty) {
mGnssNative.injectTime(time, timeReference, uncertainty);
}
/**
* Implements {@link GnssNetworkConnectivityHandler.GnssNetworkListener#onNetworkAvailable()}
*/
private void onNetworkAvailable() {
mNtpTimeHelper.onNetworkAvailable();
// Download only if supported, (prevents an unnecessary on-boot download)
if (mSupportsPsds) {
synchronized (mLock) {
for (int psdsType : mPendingDownloadPsdsTypes) {
sendMessage(DOWNLOAD_PSDS_DATA, psdsType, null);
}
mPendingDownloadPsdsTypes.clear();
}
}
}
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;
LocationListener locationListener;
LocationRequest.Builder locationRequest = new LocationRequest.Builder(
LOCATION_UPDATE_MIN_TIME_INTERVAL_MILLIS).setMaxUpdates(1);
if (independentFromGnss) {
// For fast GNSS TTFF - we use an empty listener because we will rely on the passive
// network listener to actually inject the location. this prevents double injection
provider = LocationManager.NETWORK_PROVIDER;
locationListener = location -> { };
locationRequest.setQuality(LocationRequest.QUALITY_LOW_POWER);
} else {
// For Device-Based Hybrid (E911)
provider = LocationManager.FUSED_PROVIDER;
locationListener = this::injectBestLocation;
locationRequest.setQuality(LocationRequest.QUALITY_HIGH_ACCURACY);
}
// Ignore location settings if in emergency mode. This is only allowed for
// isUserEmergency request (introduced in HAL v2.0), or HAL v1.1.
if (mNIHandler.getInEmergency()) {
GnssConfiguration.HalInterfaceVersion halVersion =
mGnssConfiguration.getHalInterfaceVersion();
if (isUserEmergency || halVersion.mMajor < 2) {
locationRequest.setLocationSettingsIgnored(true);
durationMillis *= EMERGENCY_LOCATION_UPDATE_DURATION_MULTIPLIER;
}
}
locationRequest.setDurationMillis(durationMillis);
Log.i(TAG,
String.format(
"GNSS HAL Requesting location updates from %s provider for %d millis.",
provider, durationMillis));
if (locationManager.getProvider(provider) != null) {
locationManager.requestLocationUpdates(provider, locationRequest.build(),
DIRECT_EXECUTOR, locationListener);
}
}
private void injectBestLocation(Location location) {
if (DEBUG) {
Log.d(TAG, "injectBestLocation: " + location);
}
if (location.isMock()) {
return;
}
mGnssNative.injectBestLocation(location);
}
/** Returns true if the location request is too frequent. */
private boolean isRequestLocationRateLimited() {
// TODO: implement exponential backoff.
return false;
}
private void handleDownloadPsdsData(int psdsType) {
if (!mSupportsPsds) {
// native code reports psds not supported, don't try
Log.d(TAG, "handleDownloadPsdsData() called when PSDS not supported");
return;
}
if (!mNetworkConnectivityHandler.isDataNetworkConnected()) {
// try again when network is up
synchronized (mLock) {
mPendingDownloadPsdsTypes.add(psdsType);
}
return;
}
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(() -> {
GnssPsdsDownloader psdsDownloader = new GnssPsdsDownloader(
mGnssConfiguration.getProperties());
byte[] data = psdsDownloader.downloadPsdsData(psdsType);
if (data != null) {
mHandler.post(() -> {
if (DEBUG) Log.d(TAG, "calling native_inject_psds_data");
mGnssNative.injectPsdsData(data, data.length, psdsType);
synchronized (mLock) {
mPsdsBackOff.reset();
}
});
} else {
// Try download PSDS data again later according to backoff time.
// Since this is delayed and not urgent, we do not hold a wake lock here.
// The arg2 below should not be 1 otherwise the wakelock will be under-locked.
long backoffMillis;
synchronized (mLock) {
backoffMillis = mPsdsBackOff.nextBackoffMillis();
}
mHandler.sendMessageDelayed(
mHandler.obtainMessage(DOWNLOAD_PSDS_DATA, psdsType, 0, null),
backoffMillis);
}
// 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.
mDownloadPsdsWakeLock.release();
if (DEBUG) Log.d(TAG, "WakeLock released by handleDownloadPsdsData()");
} else {
Log.e(TAG, "WakeLock expired before release in "
+ "handleDownloadPsdsData()");
}
}
});
}
private void injectLocation(Location location) {
if (!location.isMock()) {
mGnssNative.injectLocation(location);
}
}
private void setSuplHostPort() {
mSuplServerHost = mGnssConfiguration.getSuplHost();
mSuplServerPort = mGnssConfiguration.getSuplPort(TCP_MIN_PORT);
if (mSuplServerHost != null
&& mSuplServerPort > TCP_MIN_PORT
&& mSuplServerPort <= TCP_MAX_PORT) {
mGnssNative.setAgpsServer(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 GNSS_POSITION_MODE_STANDALONE;
}
// MS-Based is the preferred mode for Assisted-GPS position computation, so we favor
// such mode when it is available
if (mGnssNative.getCapabilities().hasMsb() && (suplMode & AGPS_SUPL_MODE_MSB) != 0) {
return GNSS_POSITION_MODE_MS_BASED;
}
}
return GNSS_POSITION_MODE_STANDALONE;
}
private void setGpsEnabled(boolean enabled) {
synchronized (mLock) {
mGpsEnabled = enabled;
}
}
private void handleEnable() {
if (DEBUG) Log.d(TAG, "handleEnable");
boolean inited = mGnssNative.init();
if (inited) {
setGpsEnabled(true);
mSupportsPsds = mGnssNative.isPsdsSupported();
// TODO: remove the following native calls if we can make sure they are redundant.
if (mSuplServerHost != null) {
mGnssNative.setAgpsServer(GnssNetworkConnectivityHandler.AGPS_TYPE_SUPL,
mSuplServerHost, mSuplServerPort);
}
if (mC2KServerHost != null) {
mGnssNative.setAgpsServer(GnssNetworkConnectivityHandler.AGPS_TYPE_C2K,
mC2KServerHost, mC2KServerPort);
}
mBatchingEnabled = mGnssNative.initBatching() && mGnssNative.getBatchSize() > 1;
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();
stopBatching();
if (mGnssVisibilityControl != null) {
mGnssVisibilityControl.onGpsEnabledChanged(/* isEnabled= */ false);
}
// do this before releasing wakelock
mGnssNative.cleanupBatching();
mGnssNative.cleanup();
}
private void updateEnabled() {
// Generally follow location setting for current user
boolean enabled = mContext.getSystemService(LocationManager.class)
.isLocationEnabledForUser(UserHandle.CURRENT);
// .. but enable anyway, if there's an active bypass request (e.g. ELS or ADAS)
enabled |= (mProviderRequest != null
&& mProviderRequest.isActive()
&& mProviderRequest.isBypass());
// ... 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;
}
}
/**
* Returns the hardware batch size available in this hardware implementation. If the available
* size is variable, for example, based on other operations consuming memory, this is the
* minimum size guaranteed to be available for batching operations.
*/
public int getBatchSize() {
return mGnssNative.getBatchSize();
}
@Override
protected void onFlush(Runnable listener) {
boolean added = false;
synchronized (mLock) {
if (mBatchingEnabled) {
added = mFlushListeners.add(listener);
}
}
if (!added) {
listener.run();
} else {
mGnssNative.flushBatch();
}
}
@Override
public void onSetRequest(ProviderRequest request) {
mProviderRequest = request;
updateEnabled();
updateRequirements();
}
// Called when the requirements for GPS may have changed
private void updateRequirements() {
if (mProviderRequest == null || mProviderRequest.getWorkSource() == null) {
return;
}
if (DEBUG) Log.d(TAG, "setRequest " + mProviderRequest);
if (mProviderRequest.isActive() && isGpsEnabled()) {
// update client uids
updateClientUids(mProviderRequest.getWorkSource());
if (mProviderRequest.getIntervalMillis() <= Integer.MAX_VALUE) {
mFixInterval = (int) mProviderRequest.getIntervalMillis();
} else {
Log.w(TAG, "interval overflow: " + mProviderRequest.getIntervalMillis());
mFixInterval = Integer.MAX_VALUE;
}
int batchIntervalMs = max(mFixInterval, MIN_BATCH_INTERVAL_MS);
long batchLengthMs = Math.min(mProviderRequest.getMaxUpdateDelayMillis(),
MAX_BATCH_LENGTH_MS);
// apply request to GPS engine
if (mBatchingEnabled && batchLengthMs / 2 >= batchIntervalMs) {
stopNavigating();
mFixInterval = batchIntervalMs;
startBatching(batchLengthMs);
} else {
stopBatching();
if (mStarted && mGnssNative.getCapabilities().hasScheduling()) {
// change period and/or lowPowerMode
if (!setPositionMode(mPositionMode, GNSS_POSITION_RECURRENCE_PERIODIC,
mFixInterval, mProviderRequest.isLowPower())) {
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(mTimeoutListener);
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(ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT, TAG,
mTimeoutListener, mHandler);
}
}
}
} else {
updateClientUids(new WorkSource());
stopNavigating();
stopBatching();
}
}
private boolean setPositionMode(int mode, int recurrence, int minInterval,
boolean lowPowerMode) {
GnssPositionMode positionMode = new GnssPositionMode(mode, recurrence, minInterval,
0, 0, lowPowerMode);
if (mLastPositionMode != null && mLastPositionMode.equals(positionMode)) {
return true;
}
boolean result = mGnssNative.setPositionMode(mode, recurrence, minInterval, 0, 0,
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.
// TODO: this doesn't seem correct, work chain attribution tag != package?
List<WorkChain>[] diffs = WorkSource.diffChains(mClientSource, source);
if (diffs != null) {
List<WorkChain> newChains = diffs[0];
List<WorkChain> 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.
// TODO: why is GnssLocationProvider the only component using these deprecated APIs?
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.getUid(i), newWork.getPackageName(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.getUid(i),
goneWork.getPackageName(i));
}
}
}
}
@Override
public void onExtraCommand(int uid, int pid, String command, Bundle extras) {
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) {
sendMessage(DOWNLOAD_PSDS_DATA, GnssPsdsDownloader.LONG_TERM_PSDS_SERVER_INDEX,
null);
}
} else if ("request_power_stats".equals(command)) {
mGnssNative.requestPowerStats();
} else {
Log.w(TAG, "sendExtraCommand: unknown command " + command);
}
}
private void deleteAidingData(Bundle extras) {
int flags;
if (extras == null) {
flags = GNSS_AIDING_TYPE_ALL;
} else {
flags = 0;
if (extras.getBoolean("ephemeris")) flags |= GNSS_AIDING_TYPE_EPHEMERIS;
if (extras.getBoolean("almanac")) flags |= GNSS_AIDING_TYPE_ALMANAC;
if (extras.getBoolean("position")) flags |= GNSS_AIDING_TYPE_POSITION;
if (extras.getBoolean("time")) flags |= GNSS_AIDING_TYPE_TIME;
if (extras.getBoolean("iono")) flags |= GNSS_AIDING_TYPE_IONO;
if (extras.getBoolean("utc")) flags |= GNSS_AIDING_TYPE_UTC;
if (extras.getBoolean("health")) flags |= GNSS_AIDING_TYPE_HEALTH;
if (extras.getBoolean("svdir")) flags |= GNSS_AIDING_TYPE_SVDIR;
if (extras.getBoolean("svsteer")) flags |= GNSS_AIDING_TYPE_SVSTEER;
if (extras.getBoolean("sadata")) flags |= GNSS_AIDING_TYPE_SADATA;
if (extras.getBoolean("rti")) flags |= GNSS_AIDING_TYPE_RTI;
if (extras.getBoolean("celldb-info")) flags |= GNSS_AIDING_TYPE_CELLDB_INFO;
if (extras.getBoolean("all")) flags |= GNSS_AIDING_TYPE_ALL;
}
if (flags != 0) {
mGnssNative.deleteAidingData(flags);
}
}
private void startNavigating() {
if (!mStarted) {
if (DEBUG) Log.d(TAG, "startNavigating");
mTimeToFirstFix = 0;
mLastFixTime = 0;
setStarted(true);
mPositionMode = GNSS_POSITION_MODE_STANDALONE;
boolean agpsEnabled =
(Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.ASSISTED_GPS_ENABLED, 1) != 0);
mPositionMode = getSuplMode(agpsEnabled);
if (DEBUG) {
String mode;
switch (mPositionMode) {
case GNSS_POSITION_MODE_STANDALONE:
mode = "standalone";
break;
case GNSS_POSITION_MODE_MS_ASSISTED:
mode = "MS_ASSISTED";
break;
case GNSS_POSITION_MODE_MS_BASED:
mode = "MS_BASED";
break;
default:
mode = "unknown";
break;
}
Log.d(TAG, "setting position_mode to " + mode);
}
int interval = mGnssNative.getCapabilities().hasScheduling() ? mFixInterval : 1000;
if (!setPositionMode(mPositionMode, GNSS_POSITION_RECURRENCE_PERIODIC,
interval, mProviderRequest.isLowPower())) {
setStarted(false);
Log.e(TAG, "set_position_mode failed in startNavigating()");
return;
}
if (!mGnssNative.start()) {
setStarted(false);
Log.e(TAG, "native_start failed in startNavigating()");
return;
}
// reset SV count to zero
mLocationExtras.reset();
mFixRequestTime = SystemClock.elapsedRealtime();
if (!mGnssNative.getCapabilities().hasScheduling()) {
// 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(ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT, TAG, mTimeoutListener,
mHandler);
}
}
}
}
private void stopNavigating() {
if (DEBUG) Log.d(TAG, "stopNavigating");
if (mStarted) {
setStarted(false);
mGnssNative.stop();
mLastFixTime = 0;
// native_stop() may reset the position mode in hardware.
mLastPositionMode = null;
// reset SV count to zero
mLocationExtras.reset();
}
mAlarmManager.cancel(mTimeoutListener);
mAlarmManager.cancel(mWakeupListener);
}
private void startBatching(long batchLengthMs) {
long batchSize = batchLengthMs / mFixInterval;
if (DEBUG) {
Log.d(TAG, "startBatching " + mFixInterval + " " + batchLengthMs);
}
if (mGnssNative.startBatch(MILLISECONDS.toNanos(mFixInterval), true)) {
mBatchingStarted = true;
if (batchSize < getBatchSize()) {
// if the batch size is smaller than the hardware batch size, use an alarm to flush
// locations as appropriate
mBatchingAlarm = () -> {
boolean flush = false;
synchronized (mLock) {
if (mBatchingAlarm != null) {
flush = true;
mAlarmManager.setExact(ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + batchLengthMs, TAG,
mBatchingAlarm, FgThread.getHandler());
}
}
if (flush) {
mGnssNative.flushBatch();
}
};
mAlarmManager.setExact(ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + batchLengthMs, TAG,
mBatchingAlarm, FgThread.getHandler());
}
} else {
Log.e(TAG, "native_start_batch failed in startBatching()");
}
}
private void stopBatching() {
if (DEBUG) Log.d(TAG, "stopBatching");
if (mBatchingStarted) {
if (mBatchingAlarm != null) {
mAlarmManager.cancel(mBatchingAlarm);
mBatchingAlarm = null;
}
mGnssNative.stopBatch();
mBatchingStarted = false;
}
}
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();
long now = SystemClock.elapsedRealtime();
mAlarmManager.set(ELAPSED_REALTIME_WAKEUP, now + mFixInterval, TAG,
mWakeupListener, mHandler);
}
private void handleReportLocation(boolean hasLatLong, Location location) {
if (VERBOSE) Log.v(TAG, "reportLocation " + location.toString());
location.setExtras(mLocationExtras.getBundle());
reportLocation(LocationResult.wrap(location).validate());
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);
}
}
if (mStarted) {
// 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 (!mGnssNative.getCapabilities().hasScheduling() && mFixInterval < NO_FIX_TIMEOUT) {
mAlarmManager.cancel(mTimeoutListener);
}
}
if (!mGnssNative.getCapabilities().hasScheduling() && mStarted
&& mFixInterval > GPS_POLLING_THRESHOLD_INTERVAL) {
if (DEBUG) Log.d(TAG, "got fix, hibernating");
hibernate();
}
}
private void handleReportSvStatus(GnssStatus gnssStatus) {
// Log CN0 as part of GNSS metrics
mGnssMetrics.logCn0(gnssStatus);
if (VERBOSE) {
Log.v(TAG, "SV count: " + gnssStatus.getSatelliteCount());
}
int usedInFixCount = 0;
int maxCn0 = 0;
int meanCn0 = 0;
for (int i = 0; i < gnssStatus.getSatelliteCount(); i++) {
if (gnssStatus.usedInFix(i)) {
++usedInFixCount;
if (gnssStatus.getCn0DbHz(i) > maxCn0) {
maxCn0 = (int) gnssStatus.getCn0DbHz(i);
}
meanCn0 += gnssStatus.getCn0DbHz(i);
mGnssMetrics.logConstellationType(gnssStatus.getConstellationType(i));
}
}
if (usedInFixCount > 0) {
meanCn0 /= usedInFixCount;
}
// return number of sats used in fix instead of total reported
mLocationExtras.set(usedInFixCount, meanCn0, maxCn0);
mGnssMetrics.logSvStatus(gnssStatus);
}
private void restartLocationRequest() {
if (DEBUG) Log.d(TAG, "restartLocationRequest");
setStarted(false);
updateRequirements();
}
//=============================================================
// 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);
}
mGnssNative.sendNiResponse(notificationId, userResponse);
FrameworkStatsLog.write(FrameworkStatsLog.GNSS_NI_EVENT_REPORTED,
FrameworkStatsLog.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. */
private 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);
FrameworkStatsLog.write(FrameworkStatsLog.GNSS_NI_EVENT_REPORTED,
FrameworkStatsLog.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);
}
private void requestUtcTime() {
if (DEBUG) Log.d(TAG, "utcTimeRequest");
sendMessage(INJECT_NTP_TIME, 0, null);
}
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;
}
mGnssNative.setAgpsReferenceLocationCellId(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.");
}
}
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(WAKELOCK_TIMEOUT_MILLIS);
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 {
ProviderHandler(Looper looper) {
super(looper, null, true /*async*/);
}
@Override
public void handleMessage(Message msg) {
int message = msg.what;
switch (message) {
case INJECT_NTP_TIME:
mNtpTimeHelper.retrieveAndInjectNtpTime();
break;
case REQUEST_LOCATION:
handleRequestLocation(msg.arg1 == 1, (boolean) msg.obj);
break;
case DOWNLOAD_PSDS_DATA:
handleDownloadPsdsData(msg.arg1);
break;
case REPORT_LOCATION:
handleReportLocation(msg.arg1 == 1, (Location) msg.obj);
break;
case REPORT_SV_STATUS:
handleReportSvStatus((GnssStatus) 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 + ")");
}
}
}
}
/**
* @return A string representing the given message ID.
*/
private String messageIdAsString(int message) {
switch (message) {
case INJECT_NTP_TIME:
return "INJECT_NTP_TIME";
case REQUEST_LOCATION:
return "REQUEST_LOCATION";
case DOWNLOAD_PSDS_DATA:
return "DOWNLOAD_PSDS_DATA";
case REPORT_LOCATION:
return "REPORT_LOCATION";
case REPORT_SV_STATUS:
return "REPORT_SV_STATUS";
default:
return "<Unknown>";
}
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
boolean dumpAll = false;
int opti = 0;
while (opti < args.length) {
String opt = args[opti];
if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') {
break;
}
opti++;
if ("-a".equals(opt)) {
dumpAll = true;
break;
}
}
pw.print("mStarted=" + mStarted + " (changed ");
TimeUtils.formatDuration(SystemClock.elapsedRealtime()
- mStartedChangedElapsedRealtime, pw);
pw.println(" ago)");
pw.println("mBatchingEnabled=" + mBatchingEnabled);
pw.println("mBatchingStarted=" + mBatchingStarted);
pw.println("mBatchSize=" + getBatchSize());
pw.println("mFixInterval=" + mFixInterval);
pw.print(mGnssMetrics.dumpGnssMetricsAsText());
if (dumpAll) {
pw.println("native internal state: ");
pw.println(" " + mGnssNative.getInternalState());
}
}
@Override
public void onHalRestarted() {
reloadGpsProperties();
if (isGpsEnabled()) {
setGpsEnabled(false);
updateEnabled();
}
}
@Override
public void onCapabilitiesChanged(GnssCapabilities oldCapabilities,
GnssCapabilities newCapabilities) {
mHandler.post(() -> {
if (mGnssNative.getCapabilities().hasOnDemandTime()) {
mNtpTimeHelper.enablePeriodicTimeInjection();
requestUtcTime();
}
restartLocationRequest();
});
}
@Override
public void onReportLocation(boolean hasLatLong, Location location) {
sendMessage(REPORT_LOCATION, hasLatLong ? 1 : 0, location);
}
@Override
public void onReportLocations(Location[] locations) {
if (DEBUG) {
Log.d(TAG, "Location batch of size " + locations.length + " reported");
}
if (locations.length > 0) {
// attempt to fix up timestamps if necessary
if (locations.length > 1) {
// check any realtimes outside of expected bounds
boolean fixRealtime = false;
for (int i = locations.length - 2; i >= 0; i--) {
long timeDeltaMs = locations[i + 1].getTime() - locations[i].getTime();
long realtimeDeltaMs = locations[i + 1].getElapsedRealtimeMillis()
- locations[i].getElapsedRealtimeMillis();
if (abs(timeDeltaMs - realtimeDeltaMs) > MAX_BATCH_TIMESTAMP_DELTA_MS) {
fixRealtime = true;
break;
}
}
if (fixRealtime) {
// sort for monotonically increasing time before fixing realtime - realtime will
// thus also be monotonically increasing
Arrays.sort(locations,
Comparator.comparingLong(Location::getTime));
long expectedDeltaMs =
locations[locations.length - 1].getTime()
- locations[locations.length - 1].getElapsedRealtimeMillis();
for (int i = locations.length - 2; i >= 0; i--) {
locations[i].setElapsedRealtimeNanos(
MILLISECONDS.toNanos(
max(locations[i].getTime() - expectedDeltaMs, 0)));
}
} else {
// sort for monotonically increasing realttime
Arrays.sort(locations,
Comparator.comparingLong(Location::getElapsedRealtimeNanos));
}
}
reportLocation(LocationResult.wrap(locations).validate());
}
Runnable[] listeners;
synchronized (mLock) {
listeners = mFlushListeners.toArray(new Runnable[0]);
mFlushListeners.clear();
}
for (Runnable listener : listeners) {
listener.run();
}
}
@Override
public void onReportSvStatus(GnssStatus gnssStatus) {
sendMessage(REPORT_SV_STATUS, 0, gnssStatus);
}
@Override
public void onReportAGpsStatus(int agpsType, int agpsStatus, byte[] suplIpAddr) {
mNetworkConnectivityHandler.onReportAGpsStatus(agpsType, agpsStatus, suplIpAddr);
}
@Override
public void onRequestPsdsDownload(int psdsType) {
sendMessage(DOWNLOAD_PSDS_DATA, psdsType, null);
}
@Override
public void onReportNiNotification(int notificationId, int niType, int notifyFlags,
int timeout, int defaultResponse, String requestorId, String text,
int requestorIdEncoding, int textEncoding) {
reportNiNotification(notificationId, niType, notifyFlags, timeout,
defaultResponse, requestorId, text, requestorIdEncoding, textEncoding);
}
@Override
public void onRequestSetID(@GnssNative.AGpsCallbacks.AgpsSetIdFlags int flags) {
TelephonyManager phone = (TelephonyManager)
mContext.getSystemService(Context.TELEPHONY_SERVICE);
int type = AGPS_SETID_TYPE_NONE;
String setId = null;
int ddSubId = SubscriptionManager.getDefaultDataSubscriptionId();
if (SubscriptionManager.isValidSubscriptionId(ddSubId)) {
phone = phone.createForSubscriptionId(ddSubId);
}
if ((flags & AGPS_REQUEST_SETID_IMSI) == AGPS_REQUEST_SETID_IMSI) {
setId = phone.getSubscriberId();
if (setId != null) {
// This means the framework has the SIM card.
type = AGPS_SETID_TYPE_IMSI;
}
} else if ((flags & AGPS_REQUEST_SETID_MSISDN) == AGPS_REQUEST_SETID_MSISDN) {
setId = phone.getLine1Number();
if (setId != null) {
// This means the framework has the SIM card.
type = AGPS_SETID_TYPE_MSISDN;
}
}
mGnssNative.setAgpsSetId(type, (setId == null) ? "" : setId);
}
@Override
public void onRequestLocation(boolean independentFromGnss, boolean isUserEmergency) {
if (DEBUG) {
Log.d(TAG, "requestLocation. independentFromGnss: " + independentFromGnss
+ ", isUserEmergency: "
+ isUserEmergency);
}
sendMessage(REQUEST_LOCATION, independentFromGnss ? 1 : 0, isUserEmergency);
}
@Override
public void onRequestUtcTime() {
requestUtcTime();
}
@Override
public void onRequestRefLocation() {
requestRefLocation();
}
@Override
public void onReportNfwNotification(String proxyAppPackageName, byte protocolStack,
String otherProtocolStackName, byte requestor, String requestorId,
byte responseType, boolean inEmergencyMode, boolean isCachedLocation) {
if (mGnssVisibilityControl == null) {
Log.e(TAG, "reportNfwNotification: mGnssVisibilityControl uninitialized.");
return;
}
mGnssVisibilityControl.reportNfwNotification(proxyAppPackageName, protocolStack,
otherProtocolStackName, requestor, requestorId, responseType, inEmergencyMode,
isCachedLocation);
}
}