GpsLocationProvider.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
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 java.io.File;
20import java.io.FileInputStream;
21import java.io.IOException;
22import java.util.ArrayList;
23import java.util.Properties;
24
25import android.content.Context;
26import android.content.Intent;
27import android.location.Criteria;
28import android.location.IGpsStatusListener;
29import android.location.Location;
30import android.location.LocationManager;
31import android.location.LocationProvider;
32import android.location.LocationProviderImpl;
33import android.net.SntpClient;
34import android.os.Bundle;
35import android.os.IBinder;
36import android.os.RemoteException;
37import android.os.SystemClock;
38import android.util.Config;
39import android.util.Log;
40
41/**
42 * A GPS implementation of LocationProvider used by LocationManager.
43 *
44 * {@hide}
45 */
46public class GpsLocationProvider extends LocationProviderImpl {
47
48    private static final String TAG = "GpsLocationProvider";
49
50    /**
51     * Broadcast intent action indicating that the GPS has either been
52     * enabled or disabled. An intent extra provides this state as a boolean,
53     * where {@code true} means enabled.
54     * @see #EXTRA_ENABLED
55     *
56     * {@hide}
57     */
58    public static final String GPS_ENABLED_CHANGE_ACTION =
59        "android.location.GPS_ENABLED_CHANGE";
60
61    /**
62     * Broadcast intent action indicating that the GPS has either started or
63     * stopped receiving GPS fixes. An intent extra provides this state as a
64     * boolean, where {@code true} means that the GPS is actively receiving fixes.
65     * @see #EXTRA_ENABLED
66     *
67     * {@hide}
68     */
69    public static final String GPS_FIX_CHANGE_ACTION =
70        "android.location.GPS_FIX_CHANGE";
71
72    /**
73     * The lookup key for a boolean that indicates whether GPS is enabled or
74     * disabled. {@code true} means GPS is enabled. Retrieve it with
75     * {@link android.content.Intent#getBooleanExtra(String,boolean)}.
76     *
77     * {@hide}
78     */
79    public static final String EXTRA_ENABLED = "enabled";
80
81    // these need to match GpsStatusValue defines in gps.h
82    private static final int GPS_STATUS_NONE = 0;
83    private static final int GPS_STATUS_SESSION_BEGIN = 1;
84    private static final int GPS_STATUS_SESSION_END = 2;
85    private static final int GPS_STATUS_ENGINE_ON = 3;
86    private static final int GPS_STATUS_ENGINE_OFF = 4;
87
88    // these need to match GpsLocationFlags enum in gps.h
89    private static final int LOCATION_INVALID = 0;
90    private static final int LOCATION_HAS_LAT_LONG = 1;
91    private static final int LOCATION_HAS_ALTITUDE = 2;
92    private static final int LOCATION_HAS_SPEED = 4;
93    private static final int LOCATION_HAS_BEARING = 8;
94    private static final int LOCATION_HAS_ACCURACY = 16;
95
96// IMPORTANT - the GPS_DELETE_* symbols here must match constants in GpsLocationProvider.java
97    private static final int GPS_DELETE_EPHEMERIS = 0x0001;
98    private static final int GPS_DELETE_ALMANAC = 0x0002;
99    private static final int GPS_DELETE_POSITION = 0x0004;
100    private static final int GPS_DELETE_TIME = 0x0008;
101    private static final int GPS_DELETE_IONO = 0x0010;
102    private static final int GPS_DELETE_UTC = 0x0020;
103    private static final int GPS_DELETE_HEALTH = 0x0040;
104    private static final int GPS_DELETE_SVDIR = 0x0080;
105    private static final int GPS_DELETE_SVSTEER = 0x0100;
106    private static final int GPS_DELETE_SADATA = 0x0200;
107    private static final int GPS_DELETE_RTI = 0x0400;
108    private static final int GPS_DELETE_CELLDB_INFO = 0x8000;
109    private static final int GPS_DELETE_ALL = 0xFFFF;
110
111    private static final String PROPERTIES_FILE = "/etc/gps.conf";
112
113    private int mLocationFlags = LOCATION_INVALID;
114
115    // current status
116    private int mStatus = TEMPORARILY_UNAVAILABLE;
117
118    // time for last status update
119    private long mStatusUpdateTime = SystemClock.elapsedRealtime();
120
121    // turn off GPS fix icon if we haven't received a fix in 10 seconds
122    private static final long RECENT_FIX_TIMEOUT = 10 * 1000;
123
124    // true if we are enabled
125    private boolean mEnabled;
126    // true if we are enabled for location updates
127    private boolean mLocationTracking;
128
129    // true if we have network connectivity
130    private boolean mNetworkAvailable;
131
132    // true if GPS is navigating
133    private boolean mNavigating;
134
135    // requested frequency of fixes, in seconds
136    private int mFixInterval = 1;
137
138    // true if we started navigation
139    private boolean mStarted;
140
141    // for calculating time to first fix
142    private long mFixRequestTime = 0;
143    // time to first fix for most recent session
144    private int mTTFF = 0;
145    // time we received our last fix
146    private long mLastFixTime;
147
148    // properties loaded from PROPERTIES_FILE
149    private Properties mProperties;
150
151    private Context mContext;
152    private Location mLocation = new Location(LocationManager.GPS_PROVIDER);
153    private Bundle mLocationExtras = new Bundle();
154    private ArrayList<Listener> mListeners = new ArrayList<Listener>();
155    private GpsEventThread mEventThread;
156    private GpsNetworkThread mNetworkThread;
157
158    // how often to request NTP time, in milliseconds
159    // current setting 4 hours
160    private static final long NTP_INTERVAL = 4*60*60*1000;
161    // how long to wait if we have a network error in NTP or XTRA downloading
162    // current setting - 5 minutes
163    private static final long RETRY_INTERVAL = 5*60*1000;
164
165    private LocationCollector mCollector;
166
167    public static boolean isSupported() {
168        return native_is_supported();
169    }
170
171    public GpsLocationProvider(Context context, LocationCollector collector) {
172        super(LocationManager.GPS_PROVIDER);
173        mContext = context;
174        mCollector = collector;
175
176        mProperties = new Properties();
177        try {
178            File file = new File(PROPERTIES_FILE);
179            FileInputStream stream = new FileInputStream(file);
180            mProperties.load(stream);
181            stream.close();
182        } catch (IOException e) {
183            Log.e(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE, e);
184        }
185    }
186
187    /**
188     * Returns true if the provider requires access to a
189     * data network (e.g., the Internet), false otherwise.
190     */
191    @Override
192    public boolean requiresNetwork() {
193        // We want updateNetworkState() to get called when the network state changes
194        // for XTRA and NTP time injection support.
195        return true;
196    }
197
198    public void updateNetworkState(int state) {
199        mNetworkAvailable = (state == LocationProvider.AVAILABLE);
200
201        if (Config.LOGD) {
202            Log.d(TAG, "updateNetworkState " + (mNetworkAvailable ? "available" : "unavailable"));
203        }
204
205        if (mNetworkAvailable && mNetworkThread != null && mEnabled) {
206            // signal the network thread when the network becomes available
207            mNetworkThread.signal();
208        }
209    }
210
211    /**
212     * Returns true if the provider requires access to a
213     * satellite-based positioning system (e.g., GPS), false
214     * otherwise.
215     */
216    @Override
217    public boolean requiresSatellite() {
218        return true;
219    }
220
221    /**
222     * Returns true if the provider requires access to an appropriate
223     * cellular network (e.g., to make use of cell tower IDs), false
224     * otherwise.
225     */
226    @Override
227    public boolean requiresCell() {
228        return false;
229    }
230
231    /**
232     * Returns true if the use of this provider may result in a
233     * monetary charge to the user, false if use is free.  It is up to
234     * each provider to give accurate information.
235     */
236    @Override
237    public boolean hasMonetaryCost() {
238        return false;
239    }
240
241    /**
242     * Returns true if the provider is able to provide altitude
243     * information, false otherwise.  A provider that reports altitude
244     * under most circumstances but may occassionally not report it
245     * should return true.
246     */
247    @Override
248    public boolean supportsAltitude() {
249        return true;
250    }
251
252    /**
253     * Returns true if the provider is able to provide speed
254     * information, false otherwise.  A provider that reports speed
255     * under most circumstances but may occassionally not report it
256     * should return true.
257     */
258    @Override
259    public boolean supportsSpeed() {
260        return true;
261    }
262
263    /**
264     * Returns true if the provider is able to provide bearing
265     * information, false otherwise.  A provider that reports bearing
266     * under most circumstances but may occassionally not report it
267     * should return true.
268     */
269    @Override
270    public boolean supportsBearing() {
271        return true;
272    }
273
274    /**
275     * Returns the power requirement for this provider.
276     *
277     * @return the power requirement for this provider, as one of the
278     * constants Criteria.POWER_REQUIREMENT_*.
279     */
280    @Override
281    public int getPowerRequirement() {
282        return Criteria.POWER_HIGH;
283    }
284
285    /**
286     * Returns the horizontal accuracy of this provider
287     *
288     * @return the accuracy of location from this provider, as one
289     * of the constants Criteria.ACCURACY_*.
290     */
291    @Override
292    public int getAccuracy() {
293        return Criteria.ACCURACY_FINE;
294    }
295
296    /**
297     * Enables this provider.  When enabled, calls to getStatus()
298     * and getLocation() must be handled.  Hardware may be started up
299     * when the provider is enabled.
300     */
301    @Override
302    public void enable() {
303        if (Config.LOGD) Log.d(TAG, "enable");
304        mEnabled = native_init();
305
306        if (mEnabled) {
307            // run event listener thread while we are enabled
308            mEventThread = new GpsEventThread();
309            mEventThread.start();
310
311            // run network thread for NTP and XTRA support
312            if (mNetworkThread == null) {
313                mNetworkThread = new GpsNetworkThread();
314                mNetworkThread.start();
315            } else {
316                mNetworkThread.signal();
317            }
318        } else {
319            Log.w(TAG, "Failed to enable location provider");
320        }
321    }
322
323    /**
324     * Disables this provider.  When disabled, calls to getStatus()
325     * and getLocation() need not be handled.  Hardware may be shut
326     * down while the provider is disabled.
327     */
328    @Override
329    public void disable() {
330        if (Config.LOGD) Log.d(TAG, "disable");
331        mEnabled = false;
332        stopNavigating();
333        native_disable();
334
335        // make sure our event thread exits
336        if (mEventThread != null) {
337            try {
338                mEventThread.join();
339            } catch (InterruptedException e) {
340                Log.w(TAG, "InterruptedException when joining mEventThread");
341            }
342            mEventThread = null;
343        }
344
345        native_cleanup();
346    }
347
348    @Override
349    public boolean isEnabled() {
350        return mEnabled;
351    }
352
353    @Override
354    public int getStatus(Bundle extras) {
355        if (extras != null) {
356            extras.putInt("satellites", mSvCount);
357        }
358        return mStatus;
359    }
360
361    private void updateStatus(int status, int svCount) {
362        if (status != mStatus || svCount != mSvCount) {
363            mStatus = status;
364            mSvCount = svCount;
365            mLocationExtras.putInt("satellites", svCount);
366            mStatusUpdateTime = SystemClock.elapsedRealtime();
367        }
368    }
369
370    @Override
371    public long getStatusUpdateTime() {
372        return mStatusUpdateTime;
373    }
374
375    @Override
376    public boolean getLocation(Location l) {
377        synchronized (mLocation) {
378            // don't report locations without latitude and longitude
379            if ((mLocationFlags & LOCATION_HAS_LAT_LONG) == 0) {
380                return false;
381            }
382            l.set(mLocation);
383            l.setExtras(mLocationExtras);
384            return true;
385        }
386    }
387
388    @Override
389    public void enableLocationTracking(boolean enable) {
390        if (mLocationTracking == enable) {
391            return;
392        }
393
394        if (enable) {
395            mFixRequestTime = System.currentTimeMillis();
396            mTTFF = 0;
397            mLastFixTime = 0;
398            startNavigating();
399        } else {
400            stopNavigating();
401        }
402        mLocationTracking = enable;
403    }
404
405    @Override
406    public boolean isLocationTracking() {
407        return mLocationTracking;
408    }
409
410    @Override
411    public void setMinTime(long minTime) {
412        super.setMinTime(minTime);
413        if (Config.LOGD) Log.d(TAG, "setMinTime " + minTime);
414
415        if (minTime >= 0) {
416            int interval = (int)(minTime/1000);
417            if (interval < 1) {
418                interval = 1;
419            }
420            mFixInterval = interval;
421            native_set_fix_frequency(mFixInterval);
422        }
423    }
424
425    private final class Listener implements IBinder.DeathRecipient {
426        final IGpsStatusListener mListener;
427
428        int mSensors = 0;
429
430        Listener(IGpsStatusListener listener) {
431            mListener = listener;
432        }
433
434        public void binderDied() {
435            if (Config.LOGD) Log.d(TAG, "GPS status listener died");
436
437            synchronized(mListeners) {
438                mListeners.remove(this);
439            }
440        }
441    }
442
443    public void addGpsStatusListener(IGpsStatusListener listener) throws RemoteException {
444        if (listener == null) throw new NullPointerException("listener is null in addGpsStatusListener");
445
446        synchronized(mListeners) {
447            IBinder binder = listener.asBinder();
448            int size = mListeners.size();
449            for (int i = 0; i < size; i++) {
450                Listener test = mListeners.get(i);
451                if (binder.equals(test.mListener.asBinder())) {
452                    // listener already added
453                    return;
454                }
455            }
456
457            Listener l = new Listener(listener);
458            binder.linkToDeath(l, 0);
459            mListeners.add(l);
460        }
461    }
462
463    public void removeGpsStatusListener(IGpsStatusListener listener) {
464        if (listener == null) throw new NullPointerException("listener is null in addGpsStatusListener");
465
466        synchronized(mListeners) {
467            IBinder binder = listener.asBinder();
468            Listener l = null;
469            int size = mListeners.size();
470            for (int i = 0; i < size && l == null; i++) {
471                Listener test = mListeners.get(i);
472                if (binder.equals(test.mListener.asBinder())) {
473                    // listener already added
474                    return;
475                }
476            }
477
478            if (l != null) {
479                mListeners.remove(l);
480                binder.unlinkToDeath(l, 0);
481            }
482        }
483    }
484
485    @Override
486    public boolean sendExtraCommand(String command, Bundle extras) {
487
488        if ("delete_aiding_data".equals(command)) {
489            return deleteAidingData(extras);
490        }
491
492        Log.w(TAG, "sendExtraCommand: unknown command " + command);
493        return false;
494    }
495
496    private boolean deleteAidingData(Bundle extras) {
497        int flags;
498
499        if (extras == null) {
500            flags = GPS_DELETE_ALL;
501        } else {
502            flags = 0;
503            if (extras.getBoolean("ephemeris")) flags |= GPS_DELETE_EPHEMERIS;
504            if (extras.getBoolean("almanac")) flags |= GPS_DELETE_ALMANAC;
505            if (extras.getBoolean("position")) flags |= GPS_DELETE_POSITION;
506            if (extras.getBoolean("time")) flags |= GPS_DELETE_TIME;
507            if (extras.getBoolean("iono")) flags |= GPS_DELETE_IONO;
508            if (extras.getBoolean("utc")) flags |= GPS_DELETE_UTC;
509            if (extras.getBoolean("health")) flags |= GPS_DELETE_HEALTH;
510            if (extras.getBoolean("svdir")) flags |= GPS_DELETE_SVDIR;
511            if (extras.getBoolean("svsteer")) flags |= GPS_DELETE_SVSTEER;
512            if (extras.getBoolean("sadata")) flags |= GPS_DELETE_SADATA;
513            if (extras.getBoolean("rti")) flags |= GPS_DELETE_RTI;
514            if (extras.getBoolean("celldb-info")) flags |= GPS_DELETE_CELLDB_INFO;
515            if (extras.getBoolean("all")) flags |= GPS_DELETE_ALL;
516        }
517
518        if (flags != 0) {
519            native_delete_aiding_data(flags);
520            return true;
521        }
522
523        return false;
524    }
525
526    public void startNavigating() {
527        if (!mStarted) {
528            if (Config.LOGV) Log.v(TAG, "startNavigating");
529            mStarted = true;
530            if (!native_start(false, mFixInterval)) {
531                mStarted = false;
532                Log.e(TAG, "native_start failed in startNavigating()");
533            }
534
535            // reset SV count to zero
536            updateStatus(TEMPORARILY_UNAVAILABLE, 0);
537        }
538    }
539
540    public void stopNavigating() {
541        if (Config.LOGV) Log.v(TAG, "stopNavigating");
542        if (mStarted) {
543            mStarted = false;
544            native_stop();
545            mTTFF = 0;
546            mLastFixTime = 0;
547            mLocationFlags = LOCATION_INVALID;
548
549            // reset SV count to zero
550            updateStatus(TEMPORARILY_UNAVAILABLE, 0);
551        }
552    }
553
554    /**
555     * called from native code to update our position.
556     */
557    private void reportLocation(int flags, double latitude, double longitude, double altitude,
558            float speed, float bearing, float accuracy, long timestamp) {
559        if (Config.LOGV) Log.v(TAG, "reportLocation lat: " + latitude + " long: " + longitude +
560                " timestamp: " + timestamp);
561
562        mLastFixTime = System.currentTimeMillis();
563        // report time to first fix
564        if (mTTFF == 0) {
565            mTTFF = (int)(mLastFixTime - mFixRequestTime);
566            if (Config.LOGD) Log.d(TAG, "TTFF: " + mTTFF);
567
568            // notify status listeners
569            synchronized(mListeners) {
570                int size = mListeners.size();
571                for (int i = 0; i < size; i++) {
572                    Listener listener = mListeners.get(i);
573                    try {
574                        listener.mListener.onFirstFix(mTTFF);
575                    } catch (RemoteException e) {
576                        Log.w(TAG, "RemoteException in stopNavigating");
577                        mListeners.remove(listener);
578                        // adjust for size of list changing
579                        size--;
580                    }
581                }
582            }
583        }
584
585        synchronized (mLocation) {
586            mLocationFlags = flags;
587            if ((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) {
588                mLocation.setLatitude(latitude);
589                mLocation.setLongitude(longitude);
590                mLocation.setTime(timestamp);
591            }
592            if ((flags & LOCATION_HAS_ALTITUDE) == LOCATION_HAS_ALTITUDE) {
593                mLocation.setAltitude(altitude);
594            } else {
595                mLocation.removeAltitude();
596            }
597            if ((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) {
598                mLocation.setSpeed(speed);
599            } else {
600                mLocation.removeSpeed();
601            }
602            if ((flags & LOCATION_HAS_BEARING) == LOCATION_HAS_BEARING) {
603                mLocation.setBearing(bearing);
604            } else {
605                mLocation.removeBearing();
606            }
607            if ((flags & LOCATION_HAS_ACCURACY) == LOCATION_HAS_ACCURACY) {
608                mLocation.setAccuracy(accuracy);
609            } else {
610                mLocation.removeAccuracy();
611            }
612
613            // Send to collector
614            if ((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) {
615                mCollector.updateLocation(mLocation);
616            }
617        }
618
619        if (mStarted && mStatus != AVAILABLE) {
620            // send an intent to notify that the GPS is receiving fixes.
621            Intent intent = new Intent(GPS_FIX_CHANGE_ACTION);
622            intent.putExtra(EXTRA_ENABLED, true);
623            mContext.sendBroadcast(intent);
624            updateStatus(AVAILABLE, mSvCount);
625        }
626   }
627
628    /**
629     * called from native code to update our status
630     */
631    private void reportStatus(int status) {
632        if (Config.LOGV) Log.v(TAG, "reportStatus status: " + status);
633
634        boolean wasNavigating = mNavigating;
635        mNavigating = (status == GPS_STATUS_SESSION_BEGIN || status == GPS_STATUS_ENGINE_ON);
636
637        if (wasNavigating != mNavigating) {
638            synchronized(mListeners) {
639                int size = mListeners.size();
640                for (int i = 0; i < size; i++) {
641                    Listener listener = mListeners.get(i);
642                    try {
643                        if (mNavigating) {
644                            listener.mListener.onGpsStarted();
645                        } else {
646                            listener.mListener.onGpsStopped();
647                        }
648                    } catch (RemoteException e) {
649                        Log.w(TAG, "RemoteException in reportStatus");
650                        mListeners.remove(listener);
651                        // adjust for size of list changing
652                        size--;
653                    }
654                }
655            }
656
657            // send an intent to notify that the GPS has been enabled or disabled.
658            Intent intent = new Intent(GPS_ENABLED_CHANGE_ACTION);
659            intent.putExtra(EXTRA_ENABLED, mNavigating);
660            mContext.sendBroadcast(intent);
661        }
662    }
663
664    /**
665     * called from native code to update SV info
666     */
667    private void reportSvStatus() {
668
669        int svCount = native_read_sv_status(mSvs, mSnrs, mSvElevations, mSvAzimuths, mSvMasks);
670
671        synchronized(mListeners) {
672            int size = mListeners.size();
673            for (int i = 0; i < size; i++) {
674                Listener listener = mListeners.get(i);
675                try {
676                    listener.mListener.onSvStatusChanged(svCount, mSvs, mSnrs,
677                            mSvElevations, mSvAzimuths, mSvMasks[EPHEMERIS_MASK],
678                            mSvMasks[ALMANAC_MASK], mSvMasks[USED_FOR_FIX_MASK]);
679                } catch (RemoteException e) {
680                    Log.w(TAG, "RemoteException in reportSvInfo");
681                    mListeners.remove(listener);
682                    // adjust for size of list changing
683                    size--;
684                }
685            }
686        }
687
688        if (Config.LOGD) {
689            if (Config.LOGV) Log.v(TAG, "SV count: " + svCount +
690                    " ephemerisMask: " + Integer.toHexString(mSvMasks[EPHEMERIS_MASK]) +
691                    " almanacMask: " + Integer.toHexString(mSvMasks[ALMANAC_MASK]));
692            for (int i = 0; i < svCount; i++) {
693                if (Config.LOGV) Log.v(TAG, "sv: " + mSvs[i] +
694                        " snr: " + (float)mSnrs[i]/10 +
695                        " elev: " + mSvElevations[i] +
696                        " azimuth: " + mSvAzimuths[i] +
697                        ((mSvMasks[EPHEMERIS_MASK] & (1 << (mSvs[i] - 1))) == 0 ? "  " : " E") +
698                        ((mSvMasks[ALMANAC_MASK] & (1 << (mSvs[i] - 1))) == 0 ? "  " : " A") +
699                        ((mSvMasks[USED_FOR_FIX_MASK] & (1 << (mSvs[i] - 1))) == 0 ? "" : "U"));
700            }
701        }
702
703        updateStatus(mStatus, svCount);
704
705        if (mNavigating && mStatus == AVAILABLE && mLastFixTime > 0 &&
706            System.currentTimeMillis() - mLastFixTime > RECENT_FIX_TIMEOUT) {
707            // send an intent to notify that the GPS is no longer receiving fixes.
708            Intent intent = new Intent(GPS_FIX_CHANGE_ACTION);
709            intent.putExtra(EXTRA_ENABLED, false);
710            mContext.sendBroadcast(intent);
711            updateStatus(TEMPORARILY_UNAVAILABLE, mSvCount);
712        }
713    }
714
715    private void xtraDownloadRequest() {
716        if (Config.LOGD) Log.d(TAG, "xtraDownloadRequest");
717        if (mNetworkThread != null) {
718            mNetworkThread.xtraDownloadRequest();
719        }
720    }
721
722    private class GpsEventThread extends Thread {
723
724        public GpsEventThread() {
725            super("GpsEventThread");
726        }
727
728        public void run() {
729            if (Config.LOGD) Log.d(TAG, "GpsEventThread starting");
730            // thread exits after disable() is called and navigation has stopped
731            while (mEnabled || mNavigating) {
732                // this will wait for an event from the GPS,
733                // which will be reported via reportLocation or reportStatus
734                native_wait_for_event();
735            }
736            if (Config.LOGD) Log.d(TAG, "GpsEventThread exiting");
737        }
738    }
739
740    private class GpsNetworkThread extends Thread {
741
742        private long mNextNtpTime = 0;
743        private long mNextXtraTime = 0;
744        private boolean mXtraDownloadRequested = false;
745
746        public GpsNetworkThread() {
747            super("GpsNetworkThread");
748        }
749
750        public void run() {
751            if (Config.LOGD) Log.d(TAG, "NetworkThread starting");
752
753            SntpClient client = new SntpClient();
754            GpsXtraDownloader xtraDownloader = null;
755
756            if (native_supports_xtra()) {
757                xtraDownloader = new GpsXtraDownloader(mContext, mProperties);
758            }
759
760            // thread exits after disable() is called
761            while (mEnabled) {
762                long waitTime = getWaitTime();
763                do {
764                    synchronized (this) {
765                        try {
766                            if (!mNetworkAvailable) {
767                                if (Config.LOGD) Log.d(TAG, "NetworkThread wait for network");
768                                wait();
769                            } else if (waitTime > 0) {
770                                if (Config.LOGD) Log.d(TAG, "NetworkThread wait for " + waitTime + "ms");
771                                wait(waitTime);
772                            }
773                        } catch (InterruptedException e) {
774                            if (Config.LOGD) Log.d(TAG, "InterruptedException in GpsNetworkThread");
775                        }
776                    }
777                    waitTime = getWaitTime();
778                } while (mEnabled && ((!mXtraDownloadRequested && waitTime > 0) || !mNetworkAvailable));
779                if (Config.LOGD) Log.d(TAG, "NetworkThread out of wake loop");
780
781                if (mEnabled) {
782                    if (mNextNtpTime <= System.currentTimeMillis()) {
783                        String ntpServer = mProperties.getProperty("NTP_SERVER", "pool.ntp.org");
784                        if (Config.LOGD) Log.d(TAG, "Requesting time from NTP server " + ntpServer);
785                        if (client.requestTime(ntpServer, 10000)) {
786                            long time = client.getNtpTime();
787                            long timeReference = client.getNtpTimeReference();
788                            int certainty = (int)(client.getRoundTripTime()/2);
789
790                            if (Config.LOGD) Log.d(TAG, "calling native_inject_time: " +
791                                    time + " reference: " + timeReference
792                                    + " certainty: " + certainty);
793
794                            native_inject_time(time, timeReference, certainty);
795                            mNextNtpTime = System.currentTimeMillis() + NTP_INTERVAL;
796                        } else {
797                            if (Config.LOGD) Log.d(TAG, "requestTime failed");
798                            mNextNtpTime = System.currentTimeMillis() + RETRY_INTERVAL;
799                        }
800                    }
801
802                    if ((mXtraDownloadRequested ||
803                            (mNextXtraTime > 0 && mNextXtraTime <= System.currentTimeMillis())) &&
804                            xtraDownloader != null) {
805                        byte[] data = xtraDownloader.downloadXtraData();
806                        if (data != null) {
807                            if (Config.LOGD) Log.d(TAG, "calling native_inject_xtra_data");
808                            native_inject_xtra_data(data, data.length);
809                            mNextXtraTime = 0;
810                            mXtraDownloadRequested = false;
811                        } else {
812                            mNextXtraTime = System.currentTimeMillis() + RETRY_INTERVAL;
813                        }
814                    }
815                }
816            }
817            if (Config.LOGD) Log.d(TAG, "NetworkThread exiting");
818        }
819
820        synchronized void xtraDownloadRequest() {
821            mXtraDownloadRequested = true;
822            notify();
823        }
824
825        synchronized void signal() {
826            notify();
827        }
828
829        private long getWaitTime() {
830            long now = System.currentTimeMillis();
831            long waitTime = mNextNtpTime - now;
832            if (mNextXtraTime != 0) {
833                long xtraWaitTime = mNextXtraTime - now;
834                if (xtraWaitTime < waitTime) {
835                    waitTime = xtraWaitTime;
836                }
837            }
838            if (waitTime < 0) {
839                waitTime = 0;
840            }
841            return waitTime;
842        }
843    }
844
845    // for GPS SV statistics
846    private static final int MAX_SVS = 32;
847    private static final int EPHEMERIS_MASK = 0;
848    private static final int ALMANAC_MASK = 1;
849    private static final int USED_FOR_FIX_MASK = 2;
850
851    // preallocated arrays, to avoid memory allocation in reportStatus()
852    private int mSvs[] = new int[MAX_SVS];
853    private float mSnrs[] = new float[MAX_SVS];
854    private float mSvElevations[] = new float[MAX_SVS];
855    private float mSvAzimuths[] = new float[MAX_SVS];
856    private int mSvMasks[] = new int[3];
857    private int mSvCount;
858
859    static { class_init_native(); }
860    private static native void class_init_native();
861    private static native boolean native_is_supported();
862
863    private native boolean native_init();
864    private native void native_disable();
865    private native void native_cleanup();
866    private native boolean native_start(boolean singleFix, int fixInterval);
867    private native boolean native_stop();
868    private native void native_set_fix_frequency(int fixFrequency);
869    private native void native_delete_aiding_data(int flags);
870    private native void native_wait_for_event();
871    // returns number of SVs
872    // mask[0] is ephemeris mask and mask[1] is almanac mask
873    private native int native_read_sv_status(int[] svs, float[] snrs,
874            float[] elevations, float[] azimuths, int[] masks);
875
876    private native void native_inject_time(long time, long timeReference, int uncertainty);
877    private native boolean native_supports_xtra();
878    private native void native_inject_xtra_data(byte[] data, int length);
879}
880