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