Remove 1 Hz "heartbeat" polling of location providers from LocationManagerService. Now location providers report their location to LocationManagerService via LocationManager.setLocation() rather than waiting to be polled. This reduces GPS fix latency by up to one second. Remove LocationProvderImpl.getLocation(). Since we are no longer polling, this method is no longer necessary. BUG=1729031 Automated import of CL 144452
1003 lines
36 KiB
Java
1003 lines
36 KiB
Java
/*
|
|
* 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.internal.location;
|
|
|
|
import android.content.BroadcastReceiver;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.IntentFilter;
|
|
import android.location.Criteria;
|
|
import android.location.IGpsStatusListener;
|
|
import android.location.ILocationManager;
|
|
import android.location.Location;
|
|
import android.location.LocationManager;
|
|
import android.location.LocationProvider;
|
|
import android.location.LocationProviderImpl;
|
|
import android.net.SntpClient;
|
|
import android.os.Bundle;
|
|
import android.os.IBinder;
|
|
import android.os.RemoteException;
|
|
import android.os.SystemClock;
|
|
import android.util.Config;
|
|
import android.util.Log;
|
|
|
|
import com.android.internal.telephony.Phone;
|
|
import com.android.internal.telephony.TelephonyIntents;
|
|
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.IOException;
|
|
import java.net.InetAddress;
|
|
import java.net.UnknownHostException;
|
|
import java.util.ArrayList;
|
|
import java.util.Properties;
|
|
|
|
/**
|
|
* A GPS implementation of LocationProvider used by LocationManager.
|
|
*
|
|
* {@hide}
|
|
*/
|
|
public class GpsLocationProvider extends LocationProviderImpl {
|
|
|
|
private static final String TAG = "GpsLocationProvider";
|
|
|
|
/**
|
|
* Broadcast intent action indicating that the GPS has either been
|
|
* enabled or disabled. An intent extra provides this state as a boolean,
|
|
* where {@code true} means enabled.
|
|
* @see #EXTRA_ENABLED
|
|
*
|
|
* {@hide}
|
|
*/
|
|
public static final String GPS_ENABLED_CHANGE_ACTION =
|
|
"android.location.GPS_ENABLED_CHANGE";
|
|
|
|
/**
|
|
* Broadcast intent action indicating that the GPS has either started or
|
|
* stopped receiving GPS fixes. An intent extra provides this state as a
|
|
* boolean, where {@code true} means that the GPS is actively receiving fixes.
|
|
* @see #EXTRA_ENABLED
|
|
*
|
|
* {@hide}
|
|
*/
|
|
public static final String GPS_FIX_CHANGE_ACTION =
|
|
"android.location.GPS_FIX_CHANGE";
|
|
|
|
/**
|
|
* The lookup key for a boolean that indicates whether GPS is enabled or
|
|
* disabled. {@code true} means GPS is enabled. Retrieve it with
|
|
* {@link android.content.Intent#getBooleanExtra(String,boolean)}.
|
|
*
|
|
* {@hide}
|
|
*/
|
|
public static final String EXTRA_ENABLED = "enabled";
|
|
|
|
// these need to match GpsPositionMode enum in gps.h
|
|
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 GpsStatusValue defines in gps.h
|
|
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 GpsLocationFlags enum in gps.h
|
|
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_ACCURACY = 16;
|
|
|
|
// IMPORTANT - the GPS_DELETE_* symbols here must match constants in GpsLocationProvider.java
|
|
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;
|
|
|
|
private static final String PROPERTIES_FILE = "/etc/gps.conf";
|
|
|
|
private int mLocationFlags = LOCATION_INVALID;
|
|
|
|
// current status
|
|
private int mStatus = 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;
|
|
|
|
// true if we are enabled
|
|
private boolean mEnabled;
|
|
// true if we are enabled for location updates
|
|
private boolean mLocationTracking;
|
|
|
|
// true if we have network connectivity
|
|
private boolean mNetworkAvailable;
|
|
|
|
// true if GPS is navigating
|
|
private boolean mNavigating;
|
|
|
|
// requested frequency of fixes, in seconds
|
|
private int mFixInterval = 1;
|
|
|
|
private int mPositionMode = GPS_POSITION_MODE_STANDALONE;
|
|
|
|
// true if we started navigation
|
|
private boolean mStarted;
|
|
|
|
// for calculating time to first fix
|
|
private long mFixRequestTime = 0;
|
|
// time to first fix for most recent session
|
|
private int mTTFF = 0;
|
|
// time we received our last fix
|
|
private long mLastFixTime;
|
|
|
|
// properties loaded from PROPERTIES_FILE
|
|
private Properties mProperties;
|
|
private String mNtpServer;
|
|
|
|
private Context mContext;
|
|
private Location mLocation = new Location(LocationManager.GPS_PROVIDER);
|
|
private Bundle mLocationExtras = new Bundle();
|
|
private ArrayList<Listener> mListeners = new ArrayList<Listener>();
|
|
private GpsEventThread mEventThread;
|
|
private GpsNetworkThread mNetworkThread;
|
|
private Object mNetworkThreadLock = new Object();
|
|
|
|
private String mSuplHost;
|
|
private int mSuplPort;
|
|
private boolean mSetSuplServer;
|
|
|
|
// how often to request NTP time, in milliseconds
|
|
// current setting 4 hours
|
|
private static final long NTP_INTERVAL = 4*60*60*1000;
|
|
// how long to wait if we have a network error in NTP or XTRA downloading
|
|
// current setting - 5 minutes
|
|
private static final long RETRY_INTERVAL = 5*60*1000;
|
|
|
|
private ILocationCollector mCollector;
|
|
|
|
private class TelephonyBroadcastReceiver extends BroadcastReceiver {
|
|
@Override public void onReceive(Context context, Intent intent) {
|
|
String action = intent.getAction();
|
|
|
|
if (action.equals(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
|
|
String state = intent.getStringExtra(Phone.STATE_KEY);
|
|
String apnName = intent.getStringExtra(Phone.DATA_APN_KEY);
|
|
String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY);
|
|
|
|
if (Config.LOGD) {
|
|
Log.d(TAG, "state: " + state + " apnName: " + apnName + " reason: " + reason);
|
|
}
|
|
if ("CONNECTED".equals(state)) {
|
|
native_set_supl_apn(apnName);
|
|
} else {
|
|
native_set_supl_apn("");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static boolean isSupported() {
|
|
return native_is_supported();
|
|
}
|
|
|
|
public GpsLocationProvider(Context context, ILocationManager locationManager) {
|
|
super(LocationManager.GPS_PROVIDER, locationManager);
|
|
mContext = context;
|
|
|
|
TelephonyBroadcastReceiver receiver = new TelephonyBroadcastReceiver();
|
|
IntentFilter intentFilter = new IntentFilter();
|
|
intentFilter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
|
|
context.registerReceiver(receiver, intentFilter);
|
|
|
|
mProperties = new Properties();
|
|
try {
|
|
File file = new File(PROPERTIES_FILE);
|
|
FileInputStream stream = new FileInputStream(file);
|
|
mProperties.load(stream);
|
|
stream.close();
|
|
mNtpServer = mProperties.getProperty("NTP_SERVER", null);
|
|
mSuplHost = mProperties.getProperty("SUPL_HOST");
|
|
String suplPortString = mProperties.getProperty("SUPL_PORT");
|
|
if (mSuplHost != null && suplPortString != null) {
|
|
try {
|
|
mSuplPort = Integer.parseInt(suplPortString);
|
|
mSetSuplServer = true;
|
|
} catch (NumberFormatException e) {
|
|
Log.e(TAG, "unable to parse SUPL_PORT: " + suplPortString);
|
|
}
|
|
}
|
|
} catch (IOException e) {
|
|
Log.w(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE);
|
|
}
|
|
}
|
|
|
|
public void setLocationCollector(ILocationCollector collector) {
|
|
mCollector = collector;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the provider requires access to a
|
|
* data network (e.g., the Internet), false otherwise.
|
|
*/
|
|
@Override
|
|
public boolean requiresNetwork() {
|
|
// We want updateNetworkState() to get called when the network state changes
|
|
// for XTRA and NTP time injection support.
|
|
return (mNtpServer != null || native_supports_xtra() || mSuplHost != null);
|
|
}
|
|
|
|
public void updateNetworkState(int state) {
|
|
mNetworkAvailable = (state == LocationProvider.AVAILABLE);
|
|
|
|
if (Config.LOGD) {
|
|
Log.d(TAG, "updateNetworkState " + (mNetworkAvailable ? "available" : "unavailable"));
|
|
}
|
|
|
|
if (mNetworkAvailable && mNetworkThread != null && mEnabled) {
|
|
// signal the network thread when the network becomes available
|
|
mNetworkThread.signal();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns true if the provider requires access to a
|
|
* satellite-based positioning system (e.g., GPS), false
|
|
* otherwise.
|
|
*/
|
|
@Override
|
|
public boolean requiresSatellite() {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the provider requires access to an appropriate
|
|
* cellular network (e.g., to make use of cell tower IDs), false
|
|
* otherwise.
|
|
*/
|
|
@Override
|
|
public boolean requiresCell() {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the use of this provider may result in a
|
|
* monetary charge to the user, false if use is free. It is up to
|
|
* each provider to give accurate information.
|
|
*/
|
|
@Override
|
|
public boolean hasMonetaryCost() {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the provider is able to provide altitude
|
|
* information, false otherwise. A provider that reports altitude
|
|
* under most circumstances but may occassionally not report it
|
|
* should return true.
|
|
*/
|
|
@Override
|
|
public boolean supportsAltitude() {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the provider is able to provide speed
|
|
* information, false otherwise. A provider that reports speed
|
|
* under most circumstances but may occassionally not report it
|
|
* should return true.
|
|
*/
|
|
@Override
|
|
public boolean supportsSpeed() {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the provider is able to provide bearing
|
|
* information, false otherwise. A provider that reports bearing
|
|
* under most circumstances but may occassionally not report it
|
|
* should return true.
|
|
*/
|
|
@Override
|
|
public boolean supportsBearing() {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns the power requirement for this provider.
|
|
*
|
|
* @return the power requirement for this provider, as one of the
|
|
* constants Criteria.POWER_REQUIREMENT_*.
|
|
*/
|
|
@Override
|
|
public int getPowerRequirement() {
|
|
return Criteria.POWER_HIGH;
|
|
}
|
|
|
|
/**
|
|
* Returns the horizontal accuracy of this provider
|
|
*
|
|
* @return the accuracy of location from this provider, as one
|
|
* of the constants Criteria.ACCURACY_*.
|
|
*/
|
|
@Override
|
|
public int getAccuracy() {
|
|
return Criteria.ACCURACY_FINE;
|
|
}
|
|
|
|
/**
|
|
* Enables this provider. When enabled, calls to getStatus()
|
|
* must be handled. Hardware may be started up
|
|
* when the provider is enabled.
|
|
*/
|
|
@Override
|
|
public synchronized void enable() {
|
|
if (Config.LOGD) Log.d(TAG, "enable");
|
|
if (mEnabled) return;
|
|
mEnabled = native_init();
|
|
|
|
if (mEnabled) {
|
|
// run event listener thread while we are enabled
|
|
mEventThread = new GpsEventThread();
|
|
mEventThread.start();
|
|
|
|
if (requiresNetwork()) {
|
|
// run network thread for NTP and XTRA support
|
|
if (mNetworkThread == null) {
|
|
mNetworkThread = new GpsNetworkThread();
|
|
mNetworkThread.start();
|
|
} else {
|
|
mNetworkThread.signal();
|
|
}
|
|
}
|
|
} else {
|
|
Log.w(TAG, "Failed to enable location provider");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Disables this provider. When disabled, calls to getStatus()
|
|
* need not be handled. Hardware may be shut
|
|
* down while the provider is disabled.
|
|
*/
|
|
@Override
|
|
public synchronized void disable() {
|
|
if (Config.LOGD) Log.d(TAG, "disable");
|
|
if (!mEnabled) return;
|
|
|
|
mEnabled = false;
|
|
stopNavigating();
|
|
native_disable();
|
|
|
|
// make sure our event thread exits
|
|
if (mEventThread != null) {
|
|
try {
|
|
mEventThread.join();
|
|
} catch (InterruptedException e) {
|
|
Log.w(TAG, "InterruptedException when joining mEventThread");
|
|
}
|
|
mEventThread = null;
|
|
}
|
|
|
|
if (mNetworkThread != null) {
|
|
mNetworkThread.setDone();
|
|
mNetworkThread = null;
|
|
}
|
|
|
|
// The GpsEventThread does not wait for the GPS to shutdown
|
|
// so we need to report the GPS_STATUS_ENGINE_OFF event here
|
|
if (mNavigating) {
|
|
reportStatus(GPS_STATUS_ENGINE_OFF);
|
|
}
|
|
|
|
native_cleanup();
|
|
}
|
|
|
|
@Override
|
|
public boolean isEnabled() {
|
|
return mEnabled;
|
|
}
|
|
|
|
@Override
|
|
public int getStatus(Bundle extras) {
|
|
if (extras != null) {
|
|
extras.putInt("satellites", mSvCount);
|
|
}
|
|
return mStatus;
|
|
}
|
|
|
|
private void updateStatus(int status, int svCount) {
|
|
if (status != mStatus || svCount != mSvCount) {
|
|
mStatus = status;
|
|
mSvCount = svCount;
|
|
mLocationExtras.putInt("satellites", svCount);
|
|
mStatusUpdateTime = SystemClock.elapsedRealtime();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public long getStatusUpdateTime() {
|
|
return mStatusUpdateTime;
|
|
}
|
|
|
|
@Override
|
|
public void enableLocationTracking(boolean enable) {
|
|
if (mLocationTracking == enable) {
|
|
return;
|
|
}
|
|
|
|
if (enable) {
|
|
mFixRequestTime = System.currentTimeMillis();
|
|
mTTFF = 0;
|
|
mLastFixTime = 0;
|
|
startNavigating();
|
|
} else {
|
|
stopNavigating();
|
|
}
|
|
mLocationTracking = enable;
|
|
}
|
|
|
|
@Override
|
|
public boolean isLocationTracking() {
|
|
return mLocationTracking;
|
|
}
|
|
|
|
@Override
|
|
public void setMinTime(long minTime) {
|
|
super.setMinTime(minTime);
|
|
if (Config.LOGD) Log.d(TAG, "setMinTime " + minTime);
|
|
|
|
if (minTime >= 0) {
|
|
int interval = (int)(minTime/1000);
|
|
if (interval < 1) {
|
|
interval = 1;
|
|
}
|
|
mFixInterval = interval;
|
|
native_set_fix_frequency(mFixInterval);
|
|
}
|
|
}
|
|
|
|
private final class Listener implements IBinder.DeathRecipient {
|
|
final IGpsStatusListener mListener;
|
|
|
|
int mSensors = 0;
|
|
|
|
Listener(IGpsStatusListener listener) {
|
|
mListener = listener;
|
|
}
|
|
|
|
public void binderDied() {
|
|
if (Config.LOGD) Log.d(TAG, "GPS status listener died");
|
|
|
|
synchronized(mListeners) {
|
|
mListeners.remove(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void addGpsStatusListener(IGpsStatusListener listener) throws RemoteException {
|
|
if (listener == null) throw new NullPointerException("listener is null in addGpsStatusListener");
|
|
|
|
synchronized(mListeners) {
|
|
IBinder binder = listener.asBinder();
|
|
int size = mListeners.size();
|
|
for (int i = 0; i < size; i++) {
|
|
Listener test = mListeners.get(i);
|
|
if (binder.equals(test.mListener.asBinder())) {
|
|
// listener already added
|
|
return;
|
|
}
|
|
}
|
|
|
|
Listener l = new Listener(listener);
|
|
binder.linkToDeath(l, 0);
|
|
mListeners.add(l);
|
|
}
|
|
}
|
|
|
|
public void removeGpsStatusListener(IGpsStatusListener listener) {
|
|
if (listener == null) throw new NullPointerException("listener is null in addGpsStatusListener");
|
|
|
|
synchronized(mListeners) {
|
|
IBinder binder = listener.asBinder();
|
|
Listener l = null;
|
|
int size = mListeners.size();
|
|
for (int i = 0; i < size && l == null; i++) {
|
|
Listener test = mListeners.get(i);
|
|
if (binder.equals(test.mListener.asBinder())) {
|
|
l = test;
|
|
}
|
|
}
|
|
|
|
if (l != null) {
|
|
mListeners.remove(l);
|
|
binder.unlinkToDeath(l, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean sendExtraCommand(String command, Bundle extras) {
|
|
|
|
if ("delete_aiding_data".equals(command)) {
|
|
return deleteAidingData(extras);
|
|
}
|
|
|
|
Log.w(TAG, "sendExtraCommand: unknown command " + command);
|
|
return false;
|
|
}
|
|
|
|
private boolean 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);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public void startNavigating() {
|
|
if (!mStarted) {
|
|
if (Config.LOGV) Log.v(TAG, "startNavigating");
|
|
mStarted = true;
|
|
if (!native_start(mPositionMode, false, mFixInterval)) {
|
|
mStarted = false;
|
|
Log.e(TAG, "native_start failed in startNavigating()");
|
|
}
|
|
|
|
// reset SV count to zero
|
|
updateStatus(TEMPORARILY_UNAVAILABLE, 0);
|
|
}
|
|
}
|
|
|
|
public void stopNavigating() {
|
|
if (Config.LOGV) Log.v(TAG, "stopNavigating");
|
|
if (mStarted) {
|
|
mStarted = false;
|
|
native_stop();
|
|
mTTFF = 0;
|
|
mLastFixTime = 0;
|
|
mLocationFlags = LOCATION_INVALID;
|
|
|
|
// reset SV count to zero
|
|
updateStatus(TEMPORARILY_UNAVAILABLE, 0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* called from native code to update our position.
|
|
*/
|
|
private void reportLocation(int flags, double latitude, double longitude, double altitude,
|
|
float speed, float bearing, float accuracy, long timestamp) {
|
|
if (Config.LOGV) Log.v(TAG, "reportLocation lat: " + latitude + " long: " + longitude +
|
|
" timestamp: " + timestamp);
|
|
|
|
mLastFixTime = System.currentTimeMillis();
|
|
// report time to first fix
|
|
if (mTTFF == 0 && (flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) {
|
|
mTTFF = (int)(mLastFixTime - mFixRequestTime);
|
|
if (Config.LOGD) Log.d(TAG, "TTFF: " + mTTFF);
|
|
|
|
// notify status listeners
|
|
synchronized(mListeners) {
|
|
int size = mListeners.size();
|
|
for (int i = 0; i < size; i++) {
|
|
Listener listener = mListeners.get(i);
|
|
try {
|
|
listener.mListener.onFirstFix(mTTFF);
|
|
} catch (RemoteException e) {
|
|
Log.w(TAG, "RemoteException in stopNavigating");
|
|
mListeners.remove(listener);
|
|
// adjust for size of list changing
|
|
size--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
synchronized (mLocation) {
|
|
mLocationFlags = flags;
|
|
if ((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) {
|
|
mLocation.setLatitude(latitude);
|
|
mLocation.setLongitude(longitude);
|
|
mLocation.setTime(timestamp);
|
|
}
|
|
if ((flags & LOCATION_HAS_ALTITUDE) == LOCATION_HAS_ALTITUDE) {
|
|
mLocation.setAltitude(altitude);
|
|
} else {
|
|
mLocation.removeAltitude();
|
|
}
|
|
if ((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) {
|
|
mLocation.setSpeed(speed);
|
|
} else {
|
|
mLocation.removeSpeed();
|
|
}
|
|
if ((flags & LOCATION_HAS_BEARING) == LOCATION_HAS_BEARING) {
|
|
mLocation.setBearing(bearing);
|
|
} else {
|
|
mLocation.removeBearing();
|
|
}
|
|
if ((flags & LOCATION_HAS_ACCURACY) == LOCATION_HAS_ACCURACY) {
|
|
mLocation.setAccuracy(accuracy);
|
|
} else {
|
|
mLocation.removeAccuracy();
|
|
}
|
|
|
|
reportLocationChanged(mLocation);
|
|
|
|
// Send to collector
|
|
if ((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG
|
|
&& mCollector != null) {
|
|
mCollector.updateLocation(mLocation);
|
|
}
|
|
}
|
|
|
|
if (mStarted && mStatus != AVAILABLE) {
|
|
// send an intent to notify that the GPS is receiving fixes.
|
|
Intent intent = new Intent(GPS_FIX_CHANGE_ACTION);
|
|
intent.putExtra(EXTRA_ENABLED, true);
|
|
mContext.sendBroadcast(intent);
|
|
updateStatus(AVAILABLE, mSvCount);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* called from native code to update our status
|
|
*/
|
|
private void reportStatus(int status) {
|
|
if (Config.LOGV) Log.v(TAG, "reportStatus status: " + status);
|
|
|
|
boolean wasNavigating = mNavigating;
|
|
mNavigating = (status == GPS_STATUS_SESSION_BEGIN);
|
|
|
|
if (wasNavigating != mNavigating) {
|
|
synchronized(mListeners) {
|
|
int size = mListeners.size();
|
|
for (int i = 0; i < size; i++) {
|
|
Listener listener = mListeners.get(i);
|
|
try {
|
|
if (mNavigating) {
|
|
listener.mListener.onGpsStarted();
|
|
} else {
|
|
listener.mListener.onGpsStopped();
|
|
}
|
|
} catch (RemoteException e) {
|
|
Log.w(TAG, "RemoteException in reportStatus");
|
|
mListeners.remove(listener);
|
|
// adjust for size of list changing
|
|
size--;
|
|
}
|
|
}
|
|
}
|
|
|
|
// send an intent to notify that the GPS has been enabled or disabled.
|
|
Intent intent = new Intent(GPS_ENABLED_CHANGE_ACTION);
|
|
intent.putExtra(EXTRA_ENABLED, mNavigating);
|
|
mContext.sendBroadcast(intent);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* called from native code to update SV info
|
|
*/
|
|
private void reportSvStatus() {
|
|
|
|
int svCount = native_read_sv_status(mSvs, mSnrs, mSvElevations, mSvAzimuths, mSvMasks);
|
|
|
|
synchronized(mListeners) {
|
|
int size = mListeners.size();
|
|
for (int i = 0; i < size; i++) {
|
|
Listener listener = mListeners.get(i);
|
|
try {
|
|
listener.mListener.onSvStatusChanged(svCount, mSvs, mSnrs,
|
|
mSvElevations, mSvAzimuths, mSvMasks[EPHEMERIS_MASK],
|
|
mSvMasks[ALMANAC_MASK], mSvMasks[USED_FOR_FIX_MASK]);
|
|
} catch (RemoteException e) {
|
|
Log.w(TAG, "RemoteException in reportSvInfo");
|
|
mListeners.remove(listener);
|
|
// adjust for size of list changing
|
|
size--;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Config.LOGD) {
|
|
if (Config.LOGV) Log.v(TAG, "SV count: " + svCount +
|
|
" ephemerisMask: " + Integer.toHexString(mSvMasks[EPHEMERIS_MASK]) +
|
|
" almanacMask: " + Integer.toHexString(mSvMasks[ALMANAC_MASK]));
|
|
for (int i = 0; i < svCount; i++) {
|
|
if (Config.LOGV) Log.v(TAG, "sv: " + mSvs[i] +
|
|
" snr: " + (float)mSnrs[i]/10 +
|
|
" elev: " + mSvElevations[i] +
|
|
" azimuth: " + mSvAzimuths[i] +
|
|
((mSvMasks[EPHEMERIS_MASK] & (1 << (mSvs[i] - 1))) == 0 ? " " : " E") +
|
|
((mSvMasks[ALMANAC_MASK] & (1 << (mSvs[i] - 1))) == 0 ? " " : " A") +
|
|
((mSvMasks[USED_FOR_FIX_MASK] & (1 << (mSvs[i] - 1))) == 0 ? "" : "U"));
|
|
}
|
|
}
|
|
|
|
updateStatus(mStatus, svCount);
|
|
|
|
if (mNavigating && mStatus == AVAILABLE && mLastFixTime > 0 &&
|
|
System.currentTimeMillis() - mLastFixTime > RECENT_FIX_TIMEOUT) {
|
|
// send an intent to notify that the GPS is no longer receiving fixes.
|
|
Intent intent = new Intent(GPS_FIX_CHANGE_ACTION);
|
|
intent.putExtra(EXTRA_ENABLED, false);
|
|
mContext.sendBroadcast(intent);
|
|
updateStatus(TEMPORARILY_UNAVAILABLE, mSvCount);
|
|
}
|
|
}
|
|
|
|
private void xtraDownloadRequest() {
|
|
if (Config.LOGD) Log.d(TAG, "xtraDownloadRequest");
|
|
if (mNetworkThread != null) {
|
|
mNetworkThread.xtraDownloadRequest();
|
|
}
|
|
}
|
|
|
|
private class GpsEventThread extends Thread {
|
|
|
|
public GpsEventThread() {
|
|
super("GpsEventThread");
|
|
}
|
|
|
|
public void run() {
|
|
if (Config.LOGD) Log.d(TAG, "GpsEventThread starting");
|
|
// Exit as soon as disable() is called instead of waiting for the GPS to stop.
|
|
while (mEnabled) {
|
|
// this will wait for an event from the GPS,
|
|
// which will be reported via reportLocation or reportStatus
|
|
native_wait_for_event();
|
|
}
|
|
if (Config.LOGD) Log.d(TAG, "GpsEventThread exiting");
|
|
}
|
|
}
|
|
|
|
private class GpsNetworkThread extends Thread {
|
|
|
|
private long mNextNtpTime = 0;
|
|
private long mNextXtraTime = 0;
|
|
private boolean mXtraDownloadRequested = false;
|
|
private boolean mDone = false;
|
|
|
|
public GpsNetworkThread() {
|
|
super("GpsNetworkThread");
|
|
}
|
|
|
|
public void run() {
|
|
synchronized (mNetworkThreadLock) {
|
|
if (!mDone) {
|
|
runLocked();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void runLocked() {
|
|
if (Config.LOGD) Log.d(TAG, "NetworkThread starting");
|
|
|
|
SntpClient client = new SntpClient();
|
|
GpsXtraDownloader xtraDownloader = null;
|
|
|
|
if (native_supports_xtra()) {
|
|
xtraDownloader = new GpsXtraDownloader(mContext, mProperties);
|
|
}
|
|
|
|
// thread exits after disable() is called
|
|
while (!mDone) {
|
|
long waitTime = getWaitTime();
|
|
do {
|
|
synchronized (this) {
|
|
try {
|
|
if (!mNetworkAvailable) {
|
|
if (Config.LOGD) Log.d(TAG,
|
|
"NetworkThread wait for network");
|
|
wait();
|
|
} else if (waitTime > 0) {
|
|
if (Config.LOGD) {
|
|
Log.d(TAG, "NetworkThread wait for " +
|
|
waitTime + "ms");
|
|
}
|
|
wait(waitTime);
|
|
}
|
|
} catch (InterruptedException e) {
|
|
if (Config.LOGD) {
|
|
Log.d(TAG, "InterruptedException in GpsNetworkThread");
|
|
}
|
|
}
|
|
}
|
|
waitTime = getWaitTime();
|
|
} while (!mDone && ((!mXtraDownloadRequested && !mSetSuplServer && waitTime > 0)
|
|
|| !mNetworkAvailable));
|
|
if (Config.LOGD) Log.d(TAG, "NetworkThread out of wake loop");
|
|
|
|
if (!mDone) {
|
|
if (mNtpServer != null &&
|
|
mNextNtpTime <= System.currentTimeMillis()) {
|
|
if (Config.LOGD) {
|
|
Log.d(TAG, "Requesting time from NTP server " + mNtpServer);
|
|
}
|
|
if (client.requestTime(mNtpServer, 10000)) {
|
|
long time = client.getNtpTime();
|
|
long timeReference = client.getNtpTimeReference();
|
|
int certainty = (int)(client.getRoundTripTime()/2);
|
|
|
|
if (Config.LOGD) Log.d(TAG, "calling native_inject_time: " +
|
|
time + " reference: " + timeReference
|
|
+ " certainty: " + certainty);
|
|
|
|
native_inject_time(time, timeReference, certainty);
|
|
mNextNtpTime = System.currentTimeMillis() + NTP_INTERVAL;
|
|
} else {
|
|
if (Config.LOGD) Log.d(TAG, "requestTime failed");
|
|
mNextNtpTime = System.currentTimeMillis() + RETRY_INTERVAL;
|
|
}
|
|
}
|
|
|
|
// Set the SUPL server address if we have not yet
|
|
if (mSetSuplServer) {
|
|
try {
|
|
InetAddress inetAddress = InetAddress.getByName(mSuplHost);
|
|
if (inetAddress != null) {
|
|
byte[] addrBytes = inetAddress.getAddress();
|
|
long addr = 0;
|
|
for (int i = 0; i < addrBytes.length; i++) {
|
|
int temp = addrBytes[i];
|
|
// signed -> unsigned
|
|
if (temp < 0) temp = 256 + temp;
|
|
addr = addr * 256 + temp;
|
|
}
|
|
// use MS-Based position mode if SUPL support is enabled
|
|
mPositionMode = GPS_POSITION_MODE_MS_BASED;
|
|
native_set_supl_server((int)addr, mSuplPort);
|
|
mSetSuplServer = false;
|
|
}
|
|
} catch (UnknownHostException e) {
|
|
Log.e(TAG, "unknown host for SUPL server " + mSuplHost);
|
|
}
|
|
}
|
|
|
|
if ((mXtraDownloadRequested ||
|
|
(mNextXtraTime > 0 && mNextXtraTime <= System.currentTimeMillis()))
|
|
&& xtraDownloader != null) {
|
|
byte[] data = xtraDownloader.downloadXtraData();
|
|
if (data != null) {
|
|
if (Config.LOGD) {
|
|
Log.d(TAG, "calling native_inject_xtra_data");
|
|
}
|
|
native_inject_xtra_data(data, data.length);
|
|
mNextXtraTime = 0;
|
|
mXtraDownloadRequested = false;
|
|
} else {
|
|
mNextXtraTime = System.currentTimeMillis() + RETRY_INTERVAL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (Config.LOGD) Log.d(TAG, "NetworkThread exiting");
|
|
}
|
|
|
|
synchronized void xtraDownloadRequest() {
|
|
mXtraDownloadRequested = true;
|
|
notify();
|
|
}
|
|
|
|
synchronized void signal() {
|
|
notify();
|
|
}
|
|
|
|
synchronized void setDone() {
|
|
if (Config.LOGD) Log.d(TAG, "stopping NetworkThread");
|
|
mDone = true;
|
|
notify();
|
|
}
|
|
|
|
private long getWaitTime() {
|
|
long now = System.currentTimeMillis();
|
|
long waitTime = Long.MAX_VALUE;
|
|
if (mNtpServer != null) {
|
|
waitTime = mNextNtpTime - now;
|
|
}
|
|
if (mNextXtraTime != 0) {
|
|
long xtraWaitTime = mNextXtraTime - now;
|
|
if (xtraWaitTime < waitTime) {
|
|
waitTime = xtraWaitTime;
|
|
}
|
|
}
|
|
if (waitTime < 0) {
|
|
waitTime = 0;
|
|
}
|
|
return waitTime;
|
|
}
|
|
}
|
|
|
|
// for GPS SV statistics
|
|
private static final int MAX_SVS = 32;
|
|
private static final int EPHEMERIS_MASK = 0;
|
|
private static final int ALMANAC_MASK = 1;
|
|
private static final int USED_FOR_FIX_MASK = 2;
|
|
|
|
// preallocated arrays, to avoid memory allocation in reportStatus()
|
|
private int mSvs[] = new int[MAX_SVS];
|
|
private float mSnrs[] = new float[MAX_SVS];
|
|
private float mSvElevations[] = new float[MAX_SVS];
|
|
private float mSvAzimuths[] = new float[MAX_SVS];
|
|
private int mSvMasks[] = new int[3];
|
|
private int mSvCount;
|
|
|
|
static { class_init_native(); }
|
|
private static native void class_init_native();
|
|
private static native boolean native_is_supported();
|
|
|
|
private native boolean native_init();
|
|
private native void native_disable();
|
|
private native void native_cleanup();
|
|
private native boolean native_start(int positionMode, boolean singleFix, int fixInterval);
|
|
private native boolean native_stop();
|
|
private native void native_set_fix_frequency(int fixFrequency);
|
|
private native void native_delete_aiding_data(int flags);
|
|
private native void native_wait_for_event();
|
|
// returns number of SVs
|
|
// mask[0] is ephemeris mask and mask[1] is almanac mask
|
|
private native int native_read_sv_status(int[] svs, float[] snrs,
|
|
float[] elevations, float[] azimuths, int[] masks);
|
|
|
|
// XTRA Support
|
|
private native void native_inject_time(long time, long timeReference, int uncertainty);
|
|
private native boolean native_supports_xtra();
|
|
private native void native_inject_xtra_data(byte[] data, int length);
|
|
|
|
// SUPL Support
|
|
private native void native_set_supl_server(int addr, int port);
|
|
private native void native_set_supl_apn(String apn);
|
|
}
|