GpsLocationProvider.java revision a4903f254b4711c8fc0ac5f7e3d605f4dce34f35
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.location;
18
19import android.app.AlarmManager;
20import android.app.PendingIntent;
21import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.location.Criteria;
26import android.location.IGpsStatusListener;
27import android.location.IGpsStatusProvider;
28import android.location.ILocationManager;
29import android.location.INetInitiatedListener;
30import android.location.Location;
31import android.location.LocationManager;
32import android.location.LocationProvider;
33import android.location.LocationProviderInterface;
34import android.net.ConnectivityManager;
35import android.net.NetworkInfo;
36import android.net.SntpClient;
37import android.os.Bundle;
38import android.os.IBinder;
39import android.os.PowerManager;
40import android.os.RemoteException;
41import android.os.ServiceManager;
42import android.os.SystemClock;
43import android.provider.Settings;
44import android.util.Log;
45import android.util.SparseIntArray;
46
47import com.android.internal.app.IBatteryStats;
48import com.android.internal.telephony.Phone;
49import com.android.internal.location.GpsNetInitiatedHandler;
50import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification;
51
52import java.io.File;
53import java.io.FileInputStream;
54import java.io.IOException;
55import java.io.StringBufferInputStream;
56import java.net.InetAddress;
57import java.net.UnknownHostException;
58import java.util.ArrayList;
59import java.util.Date;
60import java.util.Properties;
61import java.util.Map.Entry;
62
63/**
64 * A GPS implementation of LocationProvider used by LocationManager.
65 *
66 * {@hide}
67 */
68public class GpsLocationProvider implements LocationProviderInterface {
69
70    private static final String TAG = "GpsLocationProvider";
71
72    private static final boolean DEBUG = false;
73    private static final boolean VERBOSE = false;
74
75    /**
76     * Broadcast intent action indicating that the GPS has either been
77     * enabled or disabled. An intent extra provides this state as a boolean,
78     * where {@code true} means enabled.
79     * @see #EXTRA_ENABLED
80     *
81     * {@hide}
82     */
83    public static final String GPS_ENABLED_CHANGE_ACTION =
84        "android.location.GPS_ENABLED_CHANGE";
85
86    /**
87     * Broadcast intent action indicating that the GPS has either started or
88     * stopped receiving GPS fixes. An intent extra provides this state as a
89     * boolean, where {@code true} means that the GPS is actively receiving fixes.
90     * @see #EXTRA_ENABLED
91     *
92     * {@hide}
93     */
94    public static final String GPS_FIX_CHANGE_ACTION =
95        "android.location.GPS_FIX_CHANGE";
96
97    /**
98     * The lookup key for a boolean that indicates whether GPS is enabled or
99     * disabled. {@code true} means GPS is enabled. Retrieve it with
100     * {@link android.content.Intent#getBooleanExtra(String,boolean)}.
101     *
102     * {@hide}
103     */
104    public static final String EXTRA_ENABLED = "enabled";
105
106    // these need to match GpsPositionMode enum in gps.h
107    private static final int GPS_POSITION_MODE_STANDALONE = 0;
108    private static final int GPS_POSITION_MODE_MS_BASED = 1;
109    private static final int GPS_POSITION_MODE_MS_ASSISTED = 2;
110
111    // these need to match GpsStatusValue defines in gps.h
112    private static final int GPS_STATUS_NONE = 0;
113    private static final int GPS_STATUS_SESSION_BEGIN = 1;
114    private static final int GPS_STATUS_SESSION_END = 2;
115    private static final int GPS_STATUS_ENGINE_ON = 3;
116    private static final int GPS_STATUS_ENGINE_OFF = 4;
117
118    // these need to match GpsApgsStatusValue defines in gps.h
119    /** AGPS status event values. */
120    private static final int GPS_REQUEST_AGPS_DATA_CONN = 1;
121    private static final int GPS_RELEASE_AGPS_DATA_CONN = 2;
122    private static final int GPS_AGPS_DATA_CONNECTED = 3;
123    private static final int GPS_AGPS_DATA_CONN_DONE = 4;
124    private static final int GPS_AGPS_DATA_CONN_FAILED = 5;
125
126    // these need to match GpsLocationFlags enum in gps.h
127    private static final int LOCATION_INVALID = 0;
128    private static final int LOCATION_HAS_LAT_LONG = 1;
129    private static final int LOCATION_HAS_ALTITUDE = 2;
130    private static final int LOCATION_HAS_SPEED = 4;
131    private static final int LOCATION_HAS_BEARING = 8;
132    private static final int LOCATION_HAS_ACCURACY = 16;
133
134// IMPORTANT - the GPS_DELETE_* symbols here must match constants in gps.h
135    private static final int GPS_DELETE_EPHEMERIS = 0x0001;
136    private static final int GPS_DELETE_ALMANAC = 0x0002;
137    private static final int GPS_DELETE_POSITION = 0x0004;
138    private static final int GPS_DELETE_TIME = 0x0008;
139    private static final int GPS_DELETE_IONO = 0x0010;
140    private static final int GPS_DELETE_UTC = 0x0020;
141    private static final int GPS_DELETE_HEALTH = 0x0040;
142    private static final int GPS_DELETE_SVDIR = 0x0080;
143    private static final int GPS_DELETE_SVSTEER = 0x0100;
144    private static final int GPS_DELETE_SADATA = 0x0200;
145    private static final int GPS_DELETE_RTI = 0x0400;
146    private static final int GPS_DELETE_CELLDB_INFO = 0x8000;
147    private static final int GPS_DELETE_ALL = 0xFFFF;
148
149    // these need to match AGpsType enum in gps.h
150    private static final int AGPS_TYPE_SUPL = 1;
151    private static final int AGPS_TYPE_C2K = 2;
152
153    // for mAGpsDataConnectionState
154    private static final int AGPS_DATA_CONNECTION_CLOSED = 0;
155    private static final int AGPS_DATA_CONNECTION_OPENING = 1;
156    private static final int AGPS_DATA_CONNECTION_OPEN = 2;
157
158    private static final String PROPERTIES_FILE = "/etc/gps.conf";
159
160    private int mLocationFlags = LOCATION_INVALID;
161
162    // current status
163    private int mStatus = LocationProvider.TEMPORARILY_UNAVAILABLE;
164
165    // time for last status update
166    private long mStatusUpdateTime = SystemClock.elapsedRealtime();
167
168    // turn off GPS fix icon if we haven't received a fix in 10 seconds
169    private static final long RECENT_FIX_TIMEOUT = 10;
170
171    // number of fixes to receive before disabling GPS
172    private static final int MIN_FIX_COUNT = 10;
173
174    // stop trying if we do not receive a fix within 60 seconds
175    private static final int NO_FIX_TIMEOUT = 60;
176
177    // true if we are enabled
178    private boolean mEnabled;
179
180    // true if we have network connectivity
181    private boolean mNetworkAvailable;
182
183    // true if GPS is navigating
184    private boolean mNavigating;
185
186    // true if GPS engine is on
187    private boolean mEngineOn;
188
189    // requested frequency of fixes, in seconds
190    private int mFixInterval = 1;
191
192    // number of fixes we have received since we started navigating
193    private int mFixCount;
194
195    // true if we started navigation
196    private boolean mStarted;
197
198    // for calculating time to first fix
199    private long mFixRequestTime = 0;
200    // time to first fix for most recent session
201    private int mTTFF = 0;
202    // time we received our last fix
203    private long mLastFixTime;
204
205    // properties loaded from PROPERTIES_FILE
206    private Properties mProperties;
207    private String mNtpServer;
208    private String mSuplServerHost;
209    private int mSuplServerPort;
210    private String mC2KServerHost;
211    private int mC2KServerPort;
212
213    private final Context mContext;
214    private final ILocationManager mLocationManager;
215    private Location mLocation = new Location(LocationManager.GPS_PROVIDER);
216    private Bundle mLocationExtras = new Bundle();
217    private ArrayList<Listener> mListeners = new ArrayList<Listener>();
218    private GpsEventThread mEventThread;
219    private GpsNetworkThread mNetworkThread;
220    private Object mNetworkThreadLock = new Object();
221
222    private String mAGpsApn;
223    private int mAGpsDataConnectionState;
224    private final ConnectivityManager mConnMgr;
225    private final GpsNetInitiatedHandler mNIHandler;
226
227    // Wakelocks
228    private final static String WAKELOCK_KEY = "GpsLocationProvider";
229    private final PowerManager.WakeLock mWakeLock;
230
231    // Alarms
232    private final static String ALARM_WAKEUP = "com.android.internal.location.ALARM_WAKEUP";
233    private final static String ALARM_TIMEOUT = "com.android.internal.location.ALARM_TIMEOUT";
234    private final AlarmManager mAlarmManager;
235    private final PendingIntent mWakeupIntent;
236    private final PendingIntent mTimeoutIntent;
237
238    private final IBatteryStats mBatteryStats;
239    private final SparseIntArray mClientUids = new SparseIntArray();
240
241    // how often to request NTP time, in milliseconds
242    // current setting 4 hours
243    private static final long NTP_INTERVAL = 4*60*60*1000;
244    // how long to wait if we have a network error in NTP or XTRA downloading
245    // current setting - 5 minutes
246    private static final long RETRY_INTERVAL = 5*60*1000;
247
248    // to avoid injecting bad NTP time, we reject any time fixes that differ from system time
249    // by more than 5 minutes.
250    private static final long MAX_NTP_SYSTEM_TIME_OFFSET = 5*60*1000;
251
252    private final IGpsStatusProvider mGpsStatusProvider = new IGpsStatusProvider.Stub() {
253        public void addGpsStatusListener(IGpsStatusListener listener) throws RemoteException {
254            if (listener == null) {
255                throw new NullPointerException("listener is null in addGpsStatusListener");
256            }
257
258            synchronized(mListeners) {
259                IBinder binder = listener.asBinder();
260                int size = mListeners.size();
261                for (int i = 0; i < size; i++) {
262                    Listener test = mListeners.get(i);
263                    if (binder.equals(test.mListener.asBinder())) {
264                        // listener already added
265                        return;
266                    }
267                }
268
269                Listener l = new Listener(listener);
270                binder.linkToDeath(l, 0);
271                mListeners.add(l);
272            }
273        }
274
275        public void removeGpsStatusListener(IGpsStatusListener listener) {
276            if (listener == null) {
277                throw new NullPointerException("listener is null in addGpsStatusListener");
278            }
279
280            synchronized(mListeners) {
281                IBinder binder = listener.asBinder();
282                Listener l = null;
283                int size = mListeners.size();
284                for (int i = 0; i < size && l == null; i++) {
285                    Listener test = mListeners.get(i);
286                    if (binder.equals(test.mListener.asBinder())) {
287                        l = test;
288                    }
289                }
290
291                if (l != null) {
292                    mListeners.remove(l);
293                    binder.unlinkToDeath(l, 0);
294                }
295            }
296        }
297    };
298
299    public IGpsStatusProvider getGpsStatusProvider() {
300        return mGpsStatusProvider;
301    }
302
303    private final BroadcastReceiver mBroadcastReciever = new BroadcastReceiver() {
304        @Override public void onReceive(Context context, Intent intent) {
305            String action = intent.getAction();
306
307            if (action.equals(ALARM_WAKEUP)) {
308                if (DEBUG) Log.d(TAG, "ALARM_WAKEUP");
309                startNavigating();
310            } else if (action.equals(ALARM_TIMEOUT)) {
311                if (DEBUG) Log.d(TAG, "ALARM_TIMEOUT");
312                hibernate();
313            }
314        }
315    };
316
317    public static boolean isSupported() {
318        return native_is_supported();
319    }
320
321    public GpsLocationProvider(Context context, ILocationManager locationManager) {
322        mContext = context;
323        mLocationManager = locationManager;
324        mNIHandler= new GpsNetInitiatedHandler(context, this);
325
326        // Create a wake lock
327        PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
328        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
329
330        mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
331        mWakeupIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_WAKEUP), 0);
332        mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_TIMEOUT), 0);
333
334        IntentFilter intentFilter = new IntentFilter();
335        intentFilter.addAction(ALARM_WAKEUP);
336        intentFilter.addAction(ALARM_TIMEOUT);
337        context.registerReceiver(mBroadcastReciever, intentFilter);
338
339        mConnMgr = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
340
341        // Battery statistics service to be notified when GPS turns on or off
342        mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
343
344        mProperties = new Properties();
345        try {
346            File file = new File(PROPERTIES_FILE);
347            FileInputStream stream = new FileInputStream(file);
348            mProperties.load(stream);
349            stream.close();
350            mNtpServer = mProperties.getProperty("NTP_SERVER", null);
351
352            mSuplServerHost = mProperties.getProperty("SUPL_HOST");
353            String portString = mProperties.getProperty("SUPL_PORT");
354            if (mSuplServerHost != null && portString != null) {
355                try {
356                    mSuplServerPort = Integer.parseInt(portString);
357                } catch (NumberFormatException e) {
358                    Log.e(TAG, "unable to parse SUPL_PORT: " + portString);
359                }
360            }
361
362            mC2KServerHost = mProperties.getProperty("C2K_HOST");
363            portString = mProperties.getProperty("C2K_PORT");
364            if (mC2KServerHost != null && portString != null) {
365                try {
366                    mC2KServerPort = Integer.parseInt(portString);
367                } catch (NumberFormatException e) {
368                    Log.e(TAG, "unable to parse C2K_PORT: " + portString);
369                }
370            }
371        } catch (IOException e) {
372            Log.w(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE);
373        }
374    }
375
376    /**
377     * Returns the name of this provider.
378     */
379    public String getName() {
380        return LocationManager.GPS_PROVIDER;
381    }
382
383    /**
384     * Returns true if the provider requires access to a
385     * data network (e.g., the Internet), false otherwise.
386     */
387    public boolean requiresNetwork() {
388        return true;
389    }
390
391    public void updateNetworkState(int state, NetworkInfo info) {
392        mNetworkAvailable = (state == LocationProvider.AVAILABLE);
393
394        if (DEBUG) {
395            Log.d(TAG, "updateNetworkState " + (mNetworkAvailable ? "available" : "unavailable")
396                + " info: " + info);
397        }
398
399        if (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE_SUPL
400                && mAGpsDataConnectionState == AGPS_DATA_CONNECTION_OPENING) {
401            String apnName = info.getExtraInfo();
402            if (mNetworkAvailable && apnName != null && apnName.length() > 0) {
403                mAGpsApn = apnName;
404                if (DEBUG) Log.d(TAG, "call native_agps_data_conn_open");
405                native_agps_data_conn_open(apnName);
406                mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN;
407            } else {
408                if (DEBUG) Log.d(TAG, "call native_agps_data_conn_failed");
409                mAGpsApn = null;
410                mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED;
411                native_agps_data_conn_failed();
412            }
413        }
414
415        if (mNetworkAvailable && mNetworkThread != null && mEnabled) {
416            // signal the network thread when the network becomes available
417            mNetworkThread.signal();
418        }
419    }
420
421    /**
422     * This is called to inform us when another location provider returns a location.
423     * Someday we might use this for network location injection to aid the GPS
424     */
425    public void updateLocation(Location location) {
426        if (location.hasAccuracy()) {
427            native_inject_location(location.getLatitude(), location.getLongitude(),
428                    location.getAccuracy());
429        }
430    }
431
432    /**
433     * Returns true if the provider requires access to a
434     * satellite-based positioning system (e.g., GPS), false
435     * otherwise.
436     */
437    public boolean requiresSatellite() {
438        return true;
439    }
440
441    /**
442     * Returns true if the provider requires access to an appropriate
443     * cellular network (e.g., to make use of cell tower IDs), false
444     * otherwise.
445     */
446    public boolean requiresCell() {
447        return false;
448    }
449
450    /**
451     * Returns true if the use of this provider may result in a
452     * monetary charge to the user, false if use is free.  It is up to
453     * each provider to give accurate information.
454     */
455    public boolean hasMonetaryCost() {
456        return false;
457    }
458
459    /**
460     * Returns true if the provider is able to provide altitude
461     * information, false otherwise.  A provider that reports altitude
462     * under most circumstances but may occassionally not report it
463     * should return true.
464     */
465    public boolean supportsAltitude() {
466        return true;
467    }
468
469    /**
470     * Returns true if the provider is able to provide speed
471     * information, false otherwise.  A provider that reports speed
472     * under most circumstances but may occassionally not report it
473     * should return true.
474     */
475    public boolean supportsSpeed() {
476        return true;
477    }
478
479    /**
480     * Returns true if the provider is able to provide bearing
481     * information, false otherwise.  A provider that reports bearing
482     * under most circumstances but may occassionally not report it
483     * should return true.
484     */
485    public boolean supportsBearing() {
486        return true;
487    }
488
489    /**
490     * Returns the power requirement for this provider.
491     *
492     * @return the power requirement for this provider, as one of the
493     * constants Criteria.POWER_REQUIREMENT_*.
494     */
495    public int getPowerRequirement() {
496        return Criteria.POWER_HIGH;
497    }
498
499    /**
500     * Returns the horizontal accuracy of this provider
501     *
502     * @return the accuracy of location from this provider, as one
503     * of the constants Criteria.ACCURACY_*.
504     */
505    public int getAccuracy() {
506        return Criteria.ACCURACY_FINE;
507    }
508
509    /**
510     * Enables this provider.  When enabled, calls to getStatus()
511     * must be handled.  Hardware may be started up
512     * when the provider is enabled.
513     */
514    public synchronized void enable() {
515        if (DEBUG) Log.d(TAG, "enable");
516        if (mEnabled) return;
517        mEnabled = native_init();
518
519        if (mEnabled) {
520            if (mSuplServerHost != null) {
521                native_set_agps_server(AGPS_TYPE_SUPL, mSuplServerHost, mSuplServerPort);
522            }
523            if (mC2KServerHost != null) {
524                native_set_agps_server(AGPS_TYPE_C2K, mC2KServerHost, mC2KServerPort);
525            }
526
527            // run event listener thread while we are enabled
528            mEventThread = new GpsEventThread();
529            mEventThread.start();
530
531            if (requiresNetwork()) {
532                // run network thread for NTP and XTRA support
533                if (mNetworkThread == null) {
534                    mNetworkThread = new GpsNetworkThread();
535                    mNetworkThread.start();
536                } else {
537                    mNetworkThread.signal();
538                }
539            }
540        } else {
541            Log.w(TAG, "Failed to enable location provider");
542        }
543    }
544
545    /**
546     * Disables this provider.  When disabled, calls to getStatus()
547     * need not be handled.  Hardware may be shut
548     * down while the provider is disabled.
549     */
550    public synchronized void disable() {
551        if (DEBUG) Log.d(TAG, "disable");
552        if (!mEnabled) return;
553
554        mEnabled = false;
555        stopNavigating();
556        native_disable();
557
558        // make sure our event thread exits
559        if (mEventThread != null) {
560            try {
561                mEventThread.join();
562            } catch (InterruptedException e) {
563                Log.w(TAG, "InterruptedException when joining mEventThread");
564            }
565            mEventThread = null;
566        }
567
568        if (mNetworkThread != null) {
569            mNetworkThread.setDone();
570            mNetworkThread = null;
571        }
572
573        // do this before releasing wakelock
574        native_cleanup();
575
576        // The GpsEventThread does not wait for the GPS to shutdown
577        // so we need to report the GPS_STATUS_ENGINE_OFF event here
578        if (mNavigating) {
579            reportStatus(GPS_STATUS_SESSION_END);
580        }
581        if (mEngineOn) {
582            reportStatus(GPS_STATUS_ENGINE_OFF);
583        }
584    }
585
586    public boolean isEnabled() {
587        return mEnabled;
588    }
589
590    public int getStatus(Bundle extras) {
591        if (extras != null) {
592            extras.putInt("satellites", mSvCount);
593        }
594        return mStatus;
595    }
596
597    private void updateStatus(int status, int svCount) {
598        if (status != mStatus || svCount != mSvCount) {
599            mStatus = status;
600            mSvCount = svCount;
601            mLocationExtras.putInt("satellites", svCount);
602            mStatusUpdateTime = SystemClock.elapsedRealtime();
603        }
604    }
605
606    public long getStatusUpdateTime() {
607        return mStatusUpdateTime;
608    }
609
610    public void enableLocationTracking(boolean enable) {
611        if (enable) {
612            mTTFF = 0;
613            mLastFixTime = 0;
614            startNavigating();
615        } else {
616            mAlarmManager.cancel(mWakeupIntent);
617            mAlarmManager.cancel(mTimeoutIntent);
618            stopNavigating();
619        }
620    }
621
622    public void setMinTime(long minTime) {
623        if (DEBUG) Log.d(TAG, "setMinTime " + minTime);
624
625        if (minTime >= 0) {
626            int interval = (int)(minTime/1000);
627            if (interval < 1) {
628                interval = 1;
629            }
630            mFixInterval = interval;
631        }
632    }
633
634    private final class Listener implements IBinder.DeathRecipient {
635        final IGpsStatusListener mListener;
636
637        int mSensors = 0;
638
639        Listener(IGpsStatusListener listener) {
640            mListener = listener;
641        }
642
643        public void binderDied() {
644            if (DEBUG) Log.d(TAG, "GPS status listener died");
645
646            synchronized(mListeners) {
647                mListeners.remove(this);
648            }
649            if (mListener != null) {
650                mListener.asBinder().unlinkToDeath(this, 0);
651            }
652        }
653    }
654
655    public void addListener(int uid) {
656        synchronized(mListeners) {
657            if (mClientUids.indexOfKey(uid) >= 0) {
658                // Shouldn't be here -- already have this uid.
659                Log.w(TAG, "Duplicate add listener for uid " + uid);
660                return;
661            }
662            mClientUids.put(uid, 0);
663            if (mNavigating) {
664                try {
665                    mBatteryStats.noteStartGps(uid);
666                } catch (RemoteException e) {
667                    Log.w(TAG, "RemoteException in addListener");
668                }
669            }
670        }
671    }
672
673    public void removeListener(int uid) {
674        synchronized(mListeners) {
675            if (mClientUids.indexOfKey(uid) < 0) {
676                // Shouldn't be here -- don't have this uid.
677                Log.w(TAG, "Unneeded remove listener for uid " + uid);
678                return;
679            }
680            mClientUids.delete(uid);
681            if (mNavigating) {
682                try {
683                    mBatteryStats.noteStopGps(uid);
684                } catch (RemoteException e) {
685                    Log.w(TAG, "RemoteException in removeListener");
686                }
687            }
688        }
689    }
690
691    public boolean sendExtraCommand(String command, Bundle extras) {
692
693        if ("delete_aiding_data".equals(command)) {
694            return deleteAidingData(extras);
695        }
696        if ("force_time_injection".equals(command)) {
697            return forceTimeInjection();
698        }
699        if ("force_xtra_injection".equals(command)) {
700            if (native_supports_xtra() && mNetworkThread != null) {
701                xtraDownloadRequest();
702                return true;
703            }
704            return false;
705        }
706
707        Log.w(TAG, "sendExtraCommand: unknown command " + command);
708        return false;
709    }
710
711    private boolean deleteAidingData(Bundle extras) {
712        int flags;
713
714        if (extras == null) {
715            flags = GPS_DELETE_ALL;
716        } else {
717            flags = 0;
718            if (extras.getBoolean("ephemeris")) flags |= GPS_DELETE_EPHEMERIS;
719            if (extras.getBoolean("almanac")) flags |= GPS_DELETE_ALMANAC;
720            if (extras.getBoolean("position")) flags |= GPS_DELETE_POSITION;
721            if (extras.getBoolean("time")) flags |= GPS_DELETE_TIME;
722            if (extras.getBoolean("iono")) flags |= GPS_DELETE_IONO;
723            if (extras.getBoolean("utc")) flags |= GPS_DELETE_UTC;
724            if (extras.getBoolean("health")) flags |= GPS_DELETE_HEALTH;
725            if (extras.getBoolean("svdir")) flags |= GPS_DELETE_SVDIR;
726            if (extras.getBoolean("svsteer")) flags |= GPS_DELETE_SVSTEER;
727            if (extras.getBoolean("sadata")) flags |= GPS_DELETE_SADATA;
728            if (extras.getBoolean("rti")) flags |= GPS_DELETE_RTI;
729            if (extras.getBoolean("celldb-info")) flags |= GPS_DELETE_CELLDB_INFO;
730            if (extras.getBoolean("all")) flags |= GPS_DELETE_ALL;
731        }
732
733        if (flags != 0) {
734            native_delete_aiding_data(flags);
735            return true;
736        }
737
738        return false;
739    }
740
741    private boolean forceTimeInjection() {
742        if (DEBUG) Log.d(TAG, "forceTimeInjection");
743        if (mNetworkThread != null) {
744            mNetworkThread.timeInjectRequest();
745            return true;
746        }
747        return false;
748    }
749
750    public void startNavigating() {
751        if (!mStarted) {
752            if (DEBUG) Log.d(TAG, "startNavigating");
753            mStarted = true;
754            int positionMode;
755            if (Settings.Secure.getInt(mContext.getContentResolver(),
756                    Settings.Secure.ASSISTED_GPS_ENABLED, 1) != 0) {
757                positionMode = GPS_POSITION_MODE_MS_BASED;
758            } else {
759                positionMode = GPS_POSITION_MODE_STANDALONE;
760            }
761
762            if (!native_start(positionMode, false, mFixInterval)) {
763                mStarted = false;
764                Log.e(TAG, "native_start failed in startNavigating()");
765                return;
766            }
767
768            // reset SV count to zero
769            updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, 0);
770            mFixCount = 0;
771            mFixRequestTime = System.currentTimeMillis();
772            // set timer to give up if we do not receive a fix within NO_FIX_TIMEOUT
773            // and our fix interval is not short
774            if (mFixInterval >= NO_FIX_TIMEOUT) {
775                mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
776                        SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT * 1000, mTimeoutIntent);
777            }
778        }
779    }
780
781    public void stopNavigating() {
782        if (DEBUG) Log.d(TAG, "stopNavigating");
783        if (mStarted) {
784            mStarted = false;
785            native_stop();
786            mTTFF = 0;
787            mLastFixTime = 0;
788            mLocationFlags = LOCATION_INVALID;
789
790            // reset SV count to zero
791            updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, 0);
792        }
793    }
794
795    private void hibernate() {
796        // stop GPS until our next fix interval arrives
797        stopNavigating();
798        mFixCount = 0;
799        mAlarmManager.cancel(mTimeoutIntent);
800        mAlarmManager.cancel(mWakeupIntent);
801        long now = SystemClock.elapsedRealtime();
802        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
803                SystemClock.elapsedRealtime() + mFixInterval * 1000, mWakeupIntent);
804    }
805
806    /**
807     * called from native code to update our position.
808     */
809    private void reportLocation(int flags, double latitude, double longitude, double altitude,
810            float speed, float bearing, float accuracy, long timestamp) {
811        if (VERBOSE) Log.v(TAG, "reportLocation lat: " + latitude + " long: " + longitude +
812                " timestamp: " + timestamp);
813
814        mLastFixTime = System.currentTimeMillis();
815        // report time to first fix
816        if (mTTFF == 0 && (flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) {
817            mTTFF = (int)(mLastFixTime - mFixRequestTime);
818            if (DEBUG) Log.d(TAG, "TTFF: " + mTTFF);
819
820            // notify status listeners
821            synchronized(mListeners) {
822                int size = mListeners.size();
823                for (int i = 0; i < size; i++) {
824                    Listener listener = mListeners.get(i);
825                    try {
826                        listener.mListener.onFirstFix(mTTFF);
827                    } catch (RemoteException e) {
828                        Log.w(TAG, "RemoteException in stopNavigating");
829                        mListeners.remove(listener);
830                        // adjust for size of list changing
831                        size--;
832                    }
833                }
834            }
835        }
836
837        synchronized (mLocation) {
838            mLocationFlags = flags;
839            if ((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) {
840                mLocation.setLatitude(latitude);
841                mLocation.setLongitude(longitude);
842                mLocation.setTime(timestamp);
843            }
844            if ((flags & LOCATION_HAS_ALTITUDE) == LOCATION_HAS_ALTITUDE) {
845                mLocation.setAltitude(altitude);
846            } else {
847                mLocation.removeAltitude();
848            }
849            if ((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) {
850                mLocation.setSpeed(speed);
851            } else {
852                mLocation.removeSpeed();
853            }
854            if ((flags & LOCATION_HAS_BEARING) == LOCATION_HAS_BEARING) {
855                mLocation.setBearing(bearing);
856            } else {
857                mLocation.removeBearing();
858            }
859            if ((flags & LOCATION_HAS_ACCURACY) == LOCATION_HAS_ACCURACY) {
860                mLocation.setAccuracy(accuracy);
861            } else {
862                mLocation.removeAccuracy();
863            }
864
865            try {
866                mLocationManager.reportLocation(mLocation, false);
867            } catch (RemoteException e) {
868                Log.e(TAG, "RemoteException calling reportLocation");
869            }
870        }
871
872        if (mStarted && mStatus != LocationProvider.AVAILABLE) {
873            mAlarmManager.cancel(mTimeoutIntent);
874            // send an intent to notify that the GPS is receiving fixes.
875            Intent intent = new Intent(GPS_FIX_CHANGE_ACTION);
876            intent.putExtra(EXTRA_ENABLED, true);
877            mContext.sendBroadcast(intent);
878            updateStatus(LocationProvider.AVAILABLE, mSvCount);
879        }
880
881        if (mFixCount++ >= MIN_FIX_COUNT && mFixInterval > 1) {
882            if (DEBUG) Log.d(TAG, "exceeded MIN_FIX_COUNT");
883            hibernate();
884        }
885   }
886
887    /**
888     * called from native code to update our status
889     */
890    private void reportStatus(int status) {
891        if (VERBOSE) Log.v(TAG, "reportStatus status: " + status);
892
893        synchronized(mListeners) {
894            boolean wasNavigating = mNavigating;
895
896            switch (status) {
897                case GPS_STATUS_SESSION_BEGIN:
898                    mNavigating = true;
899                    mEngineOn = true;
900                    break;
901                case GPS_STATUS_SESSION_END:
902                    mNavigating = false;
903                    break;
904                case GPS_STATUS_ENGINE_ON:
905                    mEngineOn = true;
906                    break;
907                case GPS_STATUS_ENGINE_OFF:
908                    mEngineOn = false;
909                    mNavigating = false;
910                    break;
911            }
912
913            // beware, the events can come out of order
914            if ((mNavigating || mEngineOn) && !mWakeLock.isHeld()) {
915                if (DEBUG) Log.d(TAG, "Acquiring wakelock");
916                 mWakeLock.acquire();
917            }
918
919            if (wasNavigating != mNavigating) {
920                int size = mListeners.size();
921                for (int i = 0; i < size; i++) {
922                    Listener listener = mListeners.get(i);
923                    try {
924                        if (mNavigating) {
925                            listener.mListener.onGpsStarted();
926                        } else {
927                            listener.mListener.onGpsStopped();
928                        }
929                    } catch (RemoteException e) {
930                        Log.w(TAG, "RemoteException in reportStatus");
931                        mListeners.remove(listener);
932                        // adjust for size of list changing
933                        size--;
934                    }
935                }
936
937                try {
938                    // update battery stats
939                    for (int i=mClientUids.size() - 1; i >= 0; i--) {
940                        int uid = mClientUids.keyAt(i);
941                        if (mNavigating) {
942                            mBatteryStats.noteStartGps(uid);
943                        } else {
944                            mBatteryStats.noteStopGps(uid);
945                        }
946                    }
947                } catch (RemoteException e) {
948                    Log.w(TAG, "RemoteException in reportStatus");
949                }
950
951                // send an intent to notify that the GPS has been enabled or disabled.
952                Intent intent = new Intent(GPS_ENABLED_CHANGE_ACTION);
953                intent.putExtra(EXTRA_ENABLED, mNavigating);
954                mContext.sendBroadcast(intent);
955            }
956
957            // beware, the events can come out of order
958            if (!mNavigating && !mEngineOn && mWakeLock.isHeld()) {
959                if (DEBUG) Log.d(TAG, "Releasing wakelock");
960                mWakeLock.release();
961            }
962        }
963    }
964
965    /**
966     * called from native code to update SV info
967     */
968    private void reportSvStatus() {
969
970        int svCount = native_read_sv_status(mSvs, mSnrs, mSvElevations, mSvAzimuths, mSvMasks);
971
972        synchronized(mListeners) {
973            int size = mListeners.size();
974            for (int i = 0; i < size; i++) {
975                Listener listener = mListeners.get(i);
976                try {
977                    listener.mListener.onSvStatusChanged(svCount, mSvs, mSnrs,
978                            mSvElevations, mSvAzimuths, mSvMasks[EPHEMERIS_MASK],
979                            mSvMasks[ALMANAC_MASK], mSvMasks[USED_FOR_FIX_MASK]);
980                } catch (RemoteException e) {
981                    Log.w(TAG, "RemoteException in reportSvInfo");
982                    mListeners.remove(listener);
983                    // adjust for size of list changing
984                    size--;
985                }
986            }
987        }
988
989        if (VERBOSE) {
990            Log.v(TAG, "SV count: " + svCount +
991                    " ephemerisMask: " + Integer.toHexString(mSvMasks[EPHEMERIS_MASK]) +
992                    " almanacMask: " + Integer.toHexString(mSvMasks[ALMANAC_MASK]));
993            for (int i = 0; i < svCount; i++) {
994                Log.v(TAG, "sv: " + mSvs[i] +
995                        " snr: " + (float)mSnrs[i]/10 +
996                        " elev: " + mSvElevations[i] +
997                        " azimuth: " + mSvAzimuths[i] +
998                        ((mSvMasks[EPHEMERIS_MASK] & (1 << (mSvs[i] - 1))) == 0 ? "  " : " E") +
999                        ((mSvMasks[ALMANAC_MASK] & (1 << (mSvs[i] - 1))) == 0 ? "  " : " A") +
1000                        ((mSvMasks[USED_FOR_FIX_MASK] & (1 << (mSvs[i] - 1))) == 0 ? "" : "U"));
1001            }
1002        }
1003
1004        updateStatus(mStatus, svCount);
1005
1006        if (mNavigating && mStatus == LocationProvider.AVAILABLE && mLastFixTime > 0 &&
1007            System.currentTimeMillis() - mLastFixTime > RECENT_FIX_TIMEOUT * 1000) {
1008            // send an intent to notify that the GPS is no longer receiving fixes.
1009            Intent intent = new Intent(GPS_FIX_CHANGE_ACTION);
1010            intent.putExtra(EXTRA_ENABLED, false);
1011            mContext.sendBroadcast(intent);
1012            updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, mSvCount);
1013        }
1014    }
1015
1016    /**
1017     * called from native code to update AGPS status
1018     */
1019    private void reportAGpsStatus(int type, int status) {
1020        switch (status) {
1021            case GPS_REQUEST_AGPS_DATA_CONN:
1022                 int result = mConnMgr.startUsingNetworkFeature(
1023                        ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_SUPL);
1024                if (result == Phone.APN_ALREADY_ACTIVE) {
1025                    if (mAGpsApn != null) {
1026                        native_agps_data_conn_open(mAGpsApn);
1027                        mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN;
1028                    } else {
1029                        Log.e(TAG, "mAGpsApn not set when receiving Phone.APN_ALREADY_ACTIVE");
1030                        native_agps_data_conn_failed();
1031                    }
1032                } else if (result == Phone.APN_REQUEST_STARTED) {
1033                    mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPENING;
1034                } else {
1035                    native_agps_data_conn_failed();
1036                }
1037                break;
1038            case GPS_RELEASE_AGPS_DATA_CONN:
1039                if (mAGpsDataConnectionState != AGPS_DATA_CONNECTION_CLOSED) {
1040                    mConnMgr.stopUsingNetworkFeature(
1041                            ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_SUPL);
1042                    native_agps_data_conn_closed();
1043                    mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED;
1044                }
1045                break;
1046            case GPS_AGPS_DATA_CONNECTED:
1047                // Log.d(TAG, "GPS_AGPS_DATA_CONNECTED");
1048                break;
1049            case GPS_AGPS_DATA_CONN_DONE:
1050                // Log.d(TAG, "GPS_AGPS_DATA_CONN_DONE");
1051                break;
1052            case GPS_AGPS_DATA_CONN_FAILED:
1053                // Log.d(TAG, "GPS_AGPS_DATA_CONN_FAILED");
1054                break;
1055        }
1056    }
1057
1058    /**
1059     * called from native code to report NMEA data received
1060     */
1061    private void reportNmea(int index, long timestamp) {
1062        synchronized(mListeners) {
1063            int size = mListeners.size();
1064            if (size > 0) {
1065                // don't bother creating the String if we have no listeners
1066                int length = native_read_nmea(index, mNmeaBuffer, mNmeaBuffer.length);
1067                String nmea = new String(mNmeaBuffer, 0, length);
1068
1069                for (int i = 0; i < size; i++) {
1070                    Listener listener = mListeners.get(i);
1071                    try {
1072                        listener.mListener.onNmeaReceived(timestamp, nmea);
1073                    } catch (RemoteException e) {
1074                        Log.w(TAG, "RemoteException in reportNmea");
1075                        mListeners.remove(listener);
1076                        // adjust for size of list changing
1077                        size--;
1078                    }
1079                }
1080            }
1081        }
1082    }
1083
1084    private void xtraDownloadRequest() {
1085        if (DEBUG) Log.d(TAG, "xtraDownloadRequest");
1086        if (mNetworkThread != null) {
1087            mNetworkThread.xtraDownloadRequest();
1088        }
1089    }
1090
1091    //=============================================================
1092    // NI Client support
1093	//=============================================================
1094    private final INetInitiatedListener mNetInitiatedListener = new INetInitiatedListener.Stub() {
1095    	// Sends a response for an NI reqeust to HAL.
1096    	public boolean sendNiResponse(int notificationId, int userResponse)
1097    	{
1098        	// TODO Add Permission check
1099
1100    		StringBuilder extrasBuf = new StringBuilder();
1101
1102    		if (DEBUG) Log.d(TAG, "sendNiResponse, notifId: " + notificationId +
1103    				", response: " + userResponse);
1104
1105    		native_send_ni_response(notificationId, userResponse);
1106
1107    		return true;
1108    	}
1109    };
1110
1111    public INetInitiatedListener getNetInitiatedListener() {
1112        return mNetInitiatedListener;
1113    }
1114
1115    // Called by JNI function to report an NI request.
1116	@SuppressWarnings("deprecation")
1117	public void reportNiNotification(
1118        	int notificationId,
1119        	int niType,
1120        	int notifyFlags,
1121        	int timeout,
1122        	int defaultResponse,
1123        	String requestorId,
1124        	String text,
1125        	int requestorIdEncoding,
1126        	int textEncoding,
1127        	String extras  // Encoded extra data
1128        )
1129	{
1130		Log.i(TAG, "reportNiNotification: entered");
1131		Log.i(TAG, "notificationId: " + notificationId +
1132				", niType: " + niType +
1133				", notifyFlags: " + notifyFlags +
1134				", timeout: " + timeout +
1135				", defaultResponse: " + defaultResponse);
1136
1137		Log.i(TAG, "requestorId: " + requestorId +
1138				", text: " + text +
1139				", requestorIdEncoding: " + requestorIdEncoding +
1140				", textEncoding: " + textEncoding);
1141
1142		GpsNiNotification notification = new GpsNiNotification();
1143
1144		notification.notificationId = notificationId;
1145		notification.niType = niType;
1146		notification.needNotify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_NOTIFY) != 0;
1147		notification.needVerify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_VERIFY) != 0;
1148		notification.privacyOverride = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_PRIVACY_OVERRIDE) != 0;
1149		notification.timeout = timeout;
1150		notification.defaultResponse = defaultResponse;
1151		notification.requestorId = requestorId;
1152		notification.text = text;
1153		notification.requestorIdEncoding = requestorIdEncoding;
1154		notification.textEncoding = textEncoding;
1155
1156		// Process extras, assuming the format is
1157		// one of more lines of "key = value"
1158		Bundle bundle = new Bundle();
1159
1160		if (extras == null) extras = "";
1161		Properties extraProp = new Properties();
1162
1163		try {
1164			extraProp.load(new StringBufferInputStream(extras));
1165		}
1166		catch (IOException e)
1167		{
1168			Log.e(TAG, "reportNiNotification cannot parse extras data: " + extras);
1169		}
1170
1171		for (Entry<Object, Object> ent : extraProp.entrySet())
1172		{
1173			bundle.putString((String) ent.getKey(), (String) ent.getValue());
1174		}
1175
1176		notification.extras = bundle;
1177
1178		mNIHandler.handleNiNotification(notification);
1179	}
1180
1181    private class GpsEventThread extends Thread {
1182
1183        public GpsEventThread() {
1184            super("GpsEventThread");
1185        }
1186
1187        public void run() {
1188            if (DEBUG) Log.d(TAG, "GpsEventThread starting");
1189            // Exit as soon as disable() is called instead of waiting for the GPS to stop.
1190            while (mEnabled) {
1191                // this will wait for an event from the GPS,
1192                // which will be reported via reportLocation or reportStatus
1193                native_wait_for_event();
1194            }
1195            if (DEBUG) Log.d(TAG, "GpsEventThread exiting");
1196        }
1197    }
1198
1199    private class GpsNetworkThread extends Thread {
1200
1201        private long mNextNtpTime = 0;
1202        private long mNextXtraTime = 0;
1203        private boolean mTimeInjectRequested = false;
1204        private boolean mXtraDownloadRequested = false;
1205        private boolean mDone = false;
1206
1207        public GpsNetworkThread() {
1208            super("GpsNetworkThread");
1209        }
1210
1211        public void run() {
1212            synchronized (mNetworkThreadLock) {
1213                if (!mDone) {
1214                    runLocked();
1215                }
1216            }
1217        }
1218
1219        public void runLocked() {
1220            if (DEBUG) Log.d(TAG, "NetworkThread starting");
1221
1222            SntpClient client = new SntpClient();
1223            GpsXtraDownloader xtraDownloader = null;
1224
1225            if (native_supports_xtra()) {
1226                xtraDownloader = new GpsXtraDownloader(mContext, mProperties);
1227            }
1228
1229            // thread exits after disable() is called
1230            while (!mDone) {
1231                long waitTime = getWaitTime();
1232                do {
1233                    synchronized (this) {
1234                        try {
1235                            if (!mNetworkAvailable) {
1236                                if (DEBUG) Log.d(TAG, "NetworkThread wait for network");
1237                                wait();
1238                            } else if (waitTime > 0) {
1239                                if (DEBUG) {
1240                                    Log.d(TAG, "NetworkThread wait for " +
1241                                            waitTime + "ms");
1242                                }
1243                                wait(waitTime);
1244                            }
1245                        } catch (InterruptedException e) {
1246                            if (DEBUG) {
1247                                Log.d(TAG, "InterruptedException in GpsNetworkThread");
1248                            }
1249                        }
1250                    }
1251                    waitTime = getWaitTime();
1252                } while (!mDone && ((!mXtraDownloadRequested &&
1253                        !mTimeInjectRequested && waitTime > 0)
1254                        || !mNetworkAvailable));
1255                if (DEBUG) Log.d(TAG, "NetworkThread out of wake loop");
1256                if (!mDone) {
1257                    if (mNtpServer != null &&
1258                            (mTimeInjectRequested || mNextNtpTime <= System.currentTimeMillis())) {
1259                        if (DEBUG) {
1260                            Log.d(TAG, "Requesting time from NTP server " + mNtpServer);
1261                        }
1262                        mTimeInjectRequested = false;
1263                        if (client.requestTime(mNtpServer, 10000)) {
1264                            long time = client.getNtpTime();
1265                            long timeReference = client.getNtpTimeReference();
1266                            int certainty = (int)(client.getRoundTripTime()/2);
1267                            long now = System.currentTimeMillis();
1268                            long systemTimeOffset = time - now;
1269
1270                            Log.d(TAG, "NTP server returned: "
1271                                    + time + " (" + new Date(time)
1272                                    + ") reference: " + timeReference
1273                                    + " certainty: " + certainty
1274                                    + " system time offset: " + systemTimeOffset);
1275
1276                            // sanity check NTP time and do not use if it is too far from system time
1277                            if (systemTimeOffset < 0) {
1278                                systemTimeOffset = -systemTimeOffset;
1279                            }
1280                            if (systemTimeOffset < MAX_NTP_SYSTEM_TIME_OFFSET) {
1281                                native_inject_time(time, timeReference, certainty);
1282                            } else {
1283                                Log.e(TAG, "NTP time differs from system time by " + systemTimeOffset
1284                                        + "ms.  Ignoring.");
1285                            }
1286                            mNextNtpTime = now + NTP_INTERVAL;
1287                        } else {
1288                            if (DEBUG) Log.d(TAG, "requestTime failed");
1289                            mNextNtpTime = System.currentTimeMillis() + RETRY_INTERVAL;
1290                        }
1291                    }
1292
1293                    if ((mXtraDownloadRequested ||
1294                            (mNextXtraTime > 0 && mNextXtraTime <= System.currentTimeMillis()))
1295                            && xtraDownloader != null) {
1296                        mXtraDownloadRequested = false;
1297                        byte[] data = xtraDownloader.downloadXtraData();
1298                        if (data != null) {
1299                            if (DEBUG) {
1300                                Log.d(TAG, "calling native_inject_xtra_data");
1301                            }
1302                            native_inject_xtra_data(data, data.length);
1303                            mNextXtraTime = 0;
1304                        } else {
1305                            mNextXtraTime = System.currentTimeMillis() + RETRY_INTERVAL;
1306                        }
1307                    }
1308                }
1309            }
1310            if (DEBUG) Log.d(TAG, "NetworkThread exiting");
1311        }
1312
1313        synchronized void xtraDownloadRequest() {
1314            mXtraDownloadRequested = true;
1315            notify();
1316        }
1317
1318        synchronized void timeInjectRequest() {
1319            mTimeInjectRequested = true;
1320            notify();
1321        }
1322
1323        synchronized void signal() {
1324            notify();
1325        }
1326
1327        synchronized void setDone() {
1328            if (DEBUG) Log.d(TAG, "stopping NetworkThread");
1329            mDone = true;
1330            notify();
1331        }
1332
1333        private long getWaitTime() {
1334            long now = System.currentTimeMillis();
1335            long waitTime = Long.MAX_VALUE;
1336            if (mNtpServer != null) {
1337                waitTime = mNextNtpTime - now;
1338            }
1339            if (mNextXtraTime != 0) {
1340                long xtraWaitTime = mNextXtraTime - now;
1341                if (xtraWaitTime < waitTime) {
1342                    waitTime = xtraWaitTime;
1343                }
1344            }
1345            if (waitTime < 0) {
1346                waitTime = 0;
1347            }
1348            return waitTime;
1349        }
1350    }
1351
1352    // for GPS SV statistics
1353    private static final int MAX_SVS = 32;
1354    private static final int EPHEMERIS_MASK = 0;
1355    private static final int ALMANAC_MASK = 1;
1356    private static final int USED_FOR_FIX_MASK = 2;
1357
1358    // preallocated arrays, to avoid memory allocation in reportStatus()
1359    private int mSvs[] = new int[MAX_SVS];
1360    private float mSnrs[] = new float[MAX_SVS];
1361    private float mSvElevations[] = new float[MAX_SVS];
1362    private float mSvAzimuths[] = new float[MAX_SVS];
1363    private int mSvMasks[] = new int[3];
1364    private int mSvCount;
1365    // preallocated to avoid memory allocation in reportNmea()
1366    private byte[] mNmeaBuffer = new byte[120];
1367
1368    static { class_init_native(); }
1369    private static native void class_init_native();
1370    private static native boolean native_is_supported();
1371
1372    private native boolean native_init();
1373    private native void native_disable();
1374    private native void native_cleanup();
1375    private native boolean native_start(int positionMode, boolean singleFix, int fixInterval);
1376    private native boolean native_stop();
1377    private native void native_set_fix_frequency(int fixFrequency);
1378    private native void native_delete_aiding_data(int flags);
1379    private native void native_wait_for_event();
1380    // returns number of SVs
1381    // mask[0] is ephemeris mask and mask[1] is almanac mask
1382    private native int native_read_sv_status(int[] svs, float[] snrs,
1383            float[] elevations, float[] azimuths, int[] masks);
1384    private native int native_read_nmea(int index, byte[] buffer, int bufferSize);
1385    private native void native_inject_location(double latitude, double longitude, float accuracy);
1386
1387    // XTRA Support
1388    private native void native_inject_time(long time, long timeReference, int uncertainty);
1389    private native boolean native_supports_xtra();
1390    private native void native_inject_xtra_data(byte[] data, int length);
1391
1392    // AGPS Support
1393    private native void native_agps_data_conn_open(String apn);
1394    private native void native_agps_data_conn_closed();
1395    private native void native_agps_data_conn_failed();
1396    private native void native_set_agps_server(int type, String hostname, int port);
1397
1398    // Network-initiated (NI) Support
1399    private native void native_send_ni_response(int notificationId, int userResponse);
1400}
1401