LocationManager.java revision 00b74270c9f136a8727c5f6cda0997a3a905f385
1/*
2 * Copyright (C) 2007 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 android.location;
18
19import android.app.PendingIntent;
20import android.content.Intent;
21import android.os.Bundle;
22import android.os.Looper;
23import android.os.RemoteException;
24import android.os.Handler;
25import android.os.Message;
26import android.util.Log;
27
28import com.android.internal.location.DummyLocationProvider;
29
30import java.util.ArrayList;
31import java.util.Collections;
32import java.util.Comparator;
33import java.util.HashMap;
34import java.util.List;
35
36/**
37 * This class provides access to the system location services.  These
38 * services allow applications to obtain periodic updates of the
39 * device's geographical location, or to fire an application-specified
40 * {@link Intent} when the device enters the proximity of a given
41 * geographical location.
42 *
43 * <p>You do not
44 * instantiate this class directly; instead, retrieve it through
45 * {@link android.content.Context#getSystemService
46 * Context.getSystemService(Context.LOCATION_SERVICE)}.
47 */
48public class LocationManager {
49    private static final String TAG = "LocationManager";
50    private ILocationManager mService;
51    private final HashMap<GpsStatus.Listener, GpsStatusListenerTransport> mGpsStatusListeners =
52            new HashMap<GpsStatus.Listener, GpsStatusListenerTransport>();
53    private final HashMap<GpsStatus.NmeaListener, GpsStatusListenerTransport> mNmeaListeners =
54            new HashMap<GpsStatus.NmeaListener, GpsStatusListenerTransport>();
55    private final GpsStatus mGpsStatus = new GpsStatus();
56
57    /**
58     * Name of the network location provider.  This provider determines location based on
59     * availability of cell tower and WiFi access points. Results are retrieved
60     * by means of a network lookup.
61     *
62     * Requires either of the permissions android.permission.ACCESS_COARSE_LOCATION
63     * or android.permission.ACCESS_FINE_LOCATION.
64     */
65    public static final String NETWORK_PROVIDER = "network";
66
67    /**
68     * Name of the GPS location provider. This provider determines location using
69     * satellites. Depending on conditions, this provider may take a while to return
70     * a location fix.
71     *
72     * Requires the permission android.permission.ACCESS_FINE_LOCATION.
73     *
74     * <p> The extras Bundle for the GPS location provider can contain the
75     * following key/value pairs:
76     *
77     * <ul>
78     * <li> satellites - the number of satellites used to derive the fix
79     * </ul>
80     */
81    public static final String GPS_PROVIDER = "gps";
82
83    /**
84     * A special location provider for receiving locations without actually initiating
85     * a location fix. This provider can be used to passively receive location updates
86     * when other applications or services request them without actually requesting
87     * the locations yourself.  This provider will return locations generated by other
88     * providers.  You can query the {@link Location#getProvider()} method to determine
89     * the origin of the location update.
90     *
91     * Requires the permission android.permission.ACCESS_FINE_LOCATION, although if the GPS
92     * is not enabled this provider might only return coarse fixes.
93     */
94    public static final String PASSIVE_PROVIDER = "passive";
95
96    /**
97     * Key used for the Bundle extra holding a boolean indicating whether
98     * a proximity alert is entering (true) or exiting (false)..
99     */
100    public static final String KEY_PROXIMITY_ENTERING = "entering";
101
102    /**
103     * Key used for a Bundle extra holding an Integer status value
104     * when a status change is broadcast using a PendingIntent.
105     */
106    public static final String KEY_STATUS_CHANGED = "status";
107
108    /**
109     * Key used for a Bundle extra holding an Boolean status value
110     * when a provider enabled/disabled event is broadcast using a PendingIntent.
111     */
112    public static final String KEY_PROVIDER_ENABLED = "providerEnabled";
113
114    /**
115     * Key used for a Bundle extra holding a Location value
116     * when a location change is broadcast using a PendingIntent.
117     */
118    public static final String KEY_LOCATION_CHANGED = "location";
119
120    /**
121     * Broadcast intent action indicating that the GPS has either been
122     * enabled or disabled. An intent extra provides this state as a boolean,
123     * where {@code true} means enabled.
124     * @see #EXTRA_GPS_ENABLED
125     *
126     * {@hide}
127     */
128    public static final String GPS_ENABLED_CHANGE_ACTION =
129        "android.location.GPS_ENABLED_CHANGE";
130
131    /**
132     * Broadcast intent action indicating that the GPS has either started or
133     * stopped receiving GPS fixes. An intent extra provides this state as a
134     * boolean, where {@code true} means that the GPS is actively receiving fixes.
135     * @see #EXTRA_GPS_ENABLED
136     *
137     * {@hide}
138     */
139    public static final String GPS_FIX_CHANGE_ACTION =
140        "android.location.GPS_FIX_CHANGE";
141
142    /**
143     * The lookup key for a boolean that indicates whether GPS is enabled or
144     * disabled. {@code true} means GPS is enabled. Retrieve it with
145     * {@link android.content.Intent#getBooleanExtra(String,boolean)}.
146     *
147     * {@hide}
148     */
149    public static final String EXTRA_GPS_ENABLED = "enabled";
150
151    // Map from LocationListeners to their associated ListenerTransport objects
152    private HashMap<LocationListener,ListenerTransport> mListeners =
153        new HashMap<LocationListener,ListenerTransport>();
154
155    private class ListenerTransport extends ILocationListener.Stub {
156        private static final int TYPE_LOCATION_CHANGED = 1;
157        private static final int TYPE_STATUS_CHANGED = 2;
158        private static final int TYPE_PROVIDER_ENABLED = 3;
159        private static final int TYPE_PROVIDER_DISABLED = 4;
160
161        private LocationListener mListener;
162        private final Handler mListenerHandler;
163
164        ListenerTransport(LocationListener listener, Looper looper) {
165            mListener = listener;
166
167            if (looper == null) {
168                mListenerHandler = new Handler() {
169                    @Override
170                    public void handleMessage(Message msg) {
171                        _handleMessage(msg);
172                    }
173                };
174            } else {
175                mListenerHandler = new Handler(looper) {
176                    @Override
177                    public void handleMessage(Message msg) {
178                        _handleMessage(msg);
179                    }
180                };
181            }
182        }
183
184        public void onLocationChanged(Location location) {
185            Message msg = Message.obtain();
186            msg.what = TYPE_LOCATION_CHANGED;
187            msg.obj = location;
188            mListenerHandler.sendMessage(msg);
189        }
190
191        public void onStatusChanged(String provider, int status, Bundle extras) {
192            Message msg = Message.obtain();
193            msg.what = TYPE_STATUS_CHANGED;
194            Bundle b = new Bundle();
195            b.putString("provider", provider);
196            b.putInt("status", status);
197            if (extras != null) {
198                b.putBundle("extras", extras);
199            }
200            msg.obj = b;
201            mListenerHandler.sendMessage(msg);
202        }
203
204        public void onProviderEnabled(String provider) {
205            Message msg = Message.obtain();
206            msg.what = TYPE_PROVIDER_ENABLED;
207            msg.obj = provider;
208            mListenerHandler.sendMessage(msg);
209        }
210
211        public void onProviderDisabled(String provider) {
212            Message msg = Message.obtain();
213            msg.what = TYPE_PROVIDER_DISABLED;
214            msg.obj = provider;
215            mListenerHandler.sendMessage(msg);
216        }
217
218        private void _handleMessage(Message msg) {
219            switch (msg.what) {
220                case TYPE_LOCATION_CHANGED:
221                    Location location = new Location((Location) msg.obj);
222                    mListener.onLocationChanged(location);
223                    break;
224                case TYPE_STATUS_CHANGED:
225                    Bundle b = (Bundle) msg.obj;
226                    String provider = b.getString("provider");
227                    int status = b.getInt("status");
228                    Bundle extras = b.getBundle("extras");
229                    mListener.onStatusChanged(provider, status, extras);
230                    break;
231                case TYPE_PROVIDER_ENABLED:
232                    mListener.onProviderEnabled((String) msg.obj);
233                    break;
234                case TYPE_PROVIDER_DISABLED:
235                    mListener.onProviderDisabled((String) msg.obj);
236                    break;
237            }
238            try {
239                mService.locationCallbackFinished(this);
240            } catch (RemoteException e) {
241                Log.e(TAG, "locationCallbackFinished: RemoteException", e);
242            }
243        }
244    }
245    /**
246     * @hide - hide this constructor because it has a parameter
247     * of type ILocationManager, which is a system private class. The
248     * right way to create an instance of this class is using the
249     * factory Context.getSystemService.
250     */
251    public LocationManager(ILocationManager service) {
252        if (false) {
253            Log.d(TAG, "Constructor: service = " + service);
254        }
255        mService = service;
256    }
257
258    private LocationProvider createProvider(String name, Bundle info) {
259        DummyLocationProvider provider =
260            new DummyLocationProvider(name);
261        provider.setRequiresNetwork(info.getBoolean("network"));
262        provider.setRequiresSatellite(info.getBoolean("satellite"));
263        provider.setRequiresCell(info.getBoolean("cell"));
264        provider.setHasMonetaryCost(info.getBoolean("cost"));
265        provider.setSupportsAltitude(info.getBoolean("altitude"));
266        provider.setSupportsSpeed(info.getBoolean("speed"));
267        provider.setSupportsBearing(info.getBoolean("bearing"));
268        provider.setPowerRequirement(info.getInt("power"));
269        provider.setAccuracy(info.getInt("accuracy"));
270        return provider;
271    }
272
273    /**
274     * Returns a list of the names of all known location providers.  All
275     * providers are returned, including ones that are not permitted to be
276     * accessed by the calling activity or are currently disabled.
277     *
278     * @return list of Strings containing names of the providers
279     */
280    public List<String> getAllProviders() {
281        if (false) {
282            Log.d(TAG, "getAllProviders");
283        }
284        try {
285            return mService.getAllProviders();
286        } catch (RemoteException ex) {
287            Log.e(TAG, "getAllProviders: RemoteException", ex);
288        }
289        return null;
290    }
291
292    /**
293     * Returns a list of the names of location providers.  Only providers that
294     * are permitted to be accessed by the calling activity will be returned.
295     *
296     * @param enabledOnly if true then only the providers which are currently
297     * enabled are returned.
298     * @return list of Strings containing names of the providers
299     */
300    public List<String> getProviders(boolean enabledOnly) {
301        try {
302            return mService.getProviders(enabledOnly);
303        } catch (RemoteException ex) {
304            Log.e(TAG, "getProviders: RemoteException", ex);
305        }
306        return null;
307    }
308
309    /**
310     * Returns the information associated with the location provider of the
311     * given name, or null if no provider exists by that name.
312     *
313     * @param name the provider name
314     * @return a LocationProvider, or null
315     *
316     * @throws IllegalArgumentException if name is null
317     * @throws SecurityException if the caller is not permitted to access the
318     * given provider.
319     */
320    public LocationProvider getProvider(String name) {
321        if (name == null) {
322            throw new IllegalArgumentException("name==null");
323        }
324        try {
325            Bundle info = mService.getProviderInfo(name);
326            if (info == null) {
327                return null;
328            }
329            return createProvider(name, info);
330        } catch (RemoteException ex) {
331            Log.e(TAG, "getProvider: RemoteException", ex);
332        }
333        return null;
334    }
335
336    /**
337     * Returns a list of the names of LocationProviders that satisfy the given
338     * criteria, or null if none do.  Only providers that are permitted to be
339     * accessed by the calling activity will be returned.
340     *
341     * @param criteria the criteria that the returned providers must match
342     * @param enabledOnly if true then only the providers which are currently
343     * enabled are returned.
344     * @return list of Strings containing names of the providers
345     */
346    public List<String> getProviders(Criteria criteria, boolean enabledOnly) {
347        List<String> goodProviders = Collections.emptyList();
348        List<String> providers = getProviders(enabledOnly);
349        for (String providerName : providers) {
350            LocationProvider provider = getProvider(providerName);
351            if (provider != null && provider.meetsCriteria(criteria)) {
352                if (goodProviders.isEmpty()) {
353                    goodProviders = new ArrayList<String>();
354                }
355                goodProviders.add(providerName);
356            }
357        }
358        return goodProviders;
359    }
360
361    /**
362     * Returns the next looser power requirement, in the sequence:
363     *
364     * POWER_LOW -> POWER_MEDIUM -> POWER_HIGH -> NO_REQUIREMENT
365     */
366    private int nextPower(int power) {
367        switch (power) {
368        case Criteria.POWER_LOW:
369            return Criteria.POWER_MEDIUM;
370        case Criteria.POWER_MEDIUM:
371            return Criteria.POWER_HIGH;
372        case Criteria.POWER_HIGH:
373            return Criteria.NO_REQUIREMENT;
374        case Criteria.NO_REQUIREMENT:
375        default:
376            return Criteria.NO_REQUIREMENT;
377        }
378    }
379
380    /**
381     * Returns the next looser accuracy requirement, in the sequence:
382     *
383     * ACCURACY_FINE -> ACCURACY_APPROXIMATE-> NO_REQUIREMENT
384     */
385    private int nextAccuracy(int accuracy) {
386        if (accuracy == Criteria.ACCURACY_FINE) {
387            return Criteria.ACCURACY_COARSE;
388        } else {
389            return Criteria.NO_REQUIREMENT;
390        }
391    }
392
393    private abstract class LpComparator implements Comparator<LocationProvider> {
394
395        public int compare(int a1, int a2) {
396            if (a1 < a2) {
397                return -1;
398            } else if (a1 > a2) {
399                return 1;
400            } else {
401                return 0;
402            }
403        }
404
405        public int compare(float a1, float a2) {
406            if (a1 < a2) {
407                return -1;
408            } else if (a1 > a2) {
409                return 1;
410            } else {
411                return 0;
412            }
413        }
414    }
415
416    private class LpPowerComparator extends LpComparator {
417        public int compare(LocationProvider l1, LocationProvider l2) {
418            int a1 = l1.getPowerRequirement();
419            int a2 = l2.getPowerRequirement();
420            return compare(a1, a2); // Smaller is better
421         }
422
423         public boolean equals(LocationProvider l1, LocationProvider l2) {
424             int a1 = l1.getPowerRequirement();
425             int a2 = l2.getPowerRequirement();
426             return a1 == a2;
427         }
428    }
429
430    private class LpAccuracyComparator extends LpComparator {
431        public int compare(LocationProvider l1, LocationProvider l2) {
432            int a1 = l1.getAccuracy();
433            int a2 = l2.getAccuracy();
434            return compare(a1, a2); // Smaller is better
435         }
436
437         public boolean equals(LocationProvider l1, LocationProvider l2) {
438             int a1 = l1.getAccuracy();
439             int a2 = l2.getAccuracy();
440             return a1 == a2;
441         }
442    }
443
444    private class LpCapabilityComparator extends LpComparator {
445
446        private static final int ALTITUDE_SCORE = 4;
447        private static final int BEARING_SCORE = 4;
448        private static final int SPEED_SCORE = 4;
449
450        private int score(LocationProvider p) {
451            return (p.supportsAltitude() ? ALTITUDE_SCORE : 0) +
452                (p.supportsBearing() ? BEARING_SCORE : 0) +
453                (p.supportsSpeed() ? SPEED_SCORE : 0);
454        }
455
456        public int compare(LocationProvider l1, LocationProvider l2) {
457            int a1 = score(l1);
458            int a2 = score(l2);
459            return compare(-a1, -a2); // Bigger is better
460         }
461
462         public boolean equals(LocationProvider l1, LocationProvider l2) {
463             int a1 = score(l1);
464             int a2 = score(l2);
465             return a1 == a2;
466         }
467    }
468
469    private LocationProvider best(List<String> providerNames) {
470        List<LocationProvider> providers = new ArrayList<LocationProvider>(providerNames.size());
471        for (String name : providerNames) {
472            providers.add(getProvider(name));
473        }
474
475        if (providers.size() < 2) {
476            return providers.get(0);
477        }
478
479        // First, sort by power requirement
480        Collections.sort(providers, new LpPowerComparator());
481        int power = providers.get(0).getPowerRequirement();
482        if (power < providers.get(1).getPowerRequirement()) {
483            return providers.get(0);
484        }
485
486        int idx, size;
487
488        List<LocationProvider> tmp = new ArrayList<LocationProvider>();
489        idx = 0;
490        size = providers.size();
491        while ((idx < size) && (providers.get(idx).getPowerRequirement() == power)) {
492            tmp.add(providers.get(idx));
493            idx++;
494        }
495
496        // Next, sort by accuracy
497        Collections.sort(tmp, new LpAccuracyComparator());
498        int acc = tmp.get(0).getAccuracy();
499        if (acc < tmp.get(1).getAccuracy()) {
500            return tmp.get(0);
501        }
502
503        List<LocationProvider> tmp2 = new ArrayList<LocationProvider>();
504        idx = 0;
505        size = tmp.size();
506        while ((idx < size) && (tmp.get(idx).getAccuracy() == acc)) {
507            tmp2.add(tmp.get(idx));
508            idx++;
509        }
510
511        // Finally, sort by capability "score"
512        Collections.sort(tmp2, new LpCapabilityComparator());
513        return tmp2.get(0);
514    }
515
516    /**
517     * Returns the name of the provider that best meets the given criteria. Only providers
518     * that are permitted to be accessed by the calling activity will be
519     * returned.  If several providers meet the criteria, the one with the best
520     * accuracy is returned.  If no provider meets the criteria,
521     * the criteria are loosened in the following sequence:
522     *
523     * <ul>
524     * <li> power requirement
525     * <li> accuracy
526     * <li> bearing
527     * <li> speed
528     * <li> altitude
529     * </ul>
530     *
531     * <p> Note that the requirement on monetary cost is not removed
532     * in this process.
533     *
534     * @param criteria the criteria that need to be matched
535     * @param enabledOnly if true then only a provider that is currently enabled is returned
536     * @return name of the provider that best matches the requirements
537     */
538    public String getBestProvider(Criteria criteria, boolean enabledOnly) {
539        List<String> goodProviders = getProviders(criteria, enabledOnly);
540        if (!goodProviders.isEmpty()) {
541            return best(goodProviders).getName();
542        }
543
544        // Make a copy of the criteria that we can modify
545        criteria = new Criteria(criteria);
546
547        // Loosen power requirement
548        int power = criteria.getPowerRequirement();
549        while (goodProviders.isEmpty() && (power != Criteria.NO_REQUIREMENT)) {
550            power = nextPower(power);
551            criteria.setPowerRequirement(power);
552            goodProviders = getProviders(criteria, enabledOnly);
553        }
554        if (!goodProviders.isEmpty()) {
555            return best(goodProviders).getName();
556        }
557
558//        // Loosen response time requirement
559//        int responseTime = criteria.getPreferredResponseTime();
560//        while (goodProviders.isEmpty() &&
561//            (responseTime != Criteria.NO_REQUIREMENT)) {
562//            responseTime += 1000;
563//            if (responseTime > 60000) {
564//                responseTime = Criteria.NO_REQUIREMENT;
565//            }
566//            criteria.setPreferredResponseTime(responseTime);
567//            goodProviders = getProviders(criteria);
568//        }
569//        if (!goodProviders.isEmpty()) {
570//            return best(goodProviders);
571//        }
572
573        // Loosen accuracy requirement
574        int accuracy = criteria.getAccuracy();
575        while (goodProviders.isEmpty() && (accuracy != Criteria.NO_REQUIREMENT)) {
576            accuracy = nextAccuracy(accuracy);
577            criteria.setAccuracy(accuracy);
578            goodProviders = getProviders(criteria, enabledOnly);
579        }
580        if (!goodProviders.isEmpty()) {
581            return best(goodProviders).getName();
582        }
583
584        // Remove bearing requirement
585        criteria.setBearingRequired(false);
586        goodProviders = getProviders(criteria, enabledOnly);
587        if (!goodProviders.isEmpty()) {
588            return best(goodProviders).getName();
589        }
590
591        // Remove speed requirement
592        criteria.setSpeedRequired(false);
593        goodProviders = getProviders(criteria, enabledOnly);
594        if (!goodProviders.isEmpty()) {
595            return best(goodProviders).getName();
596        }
597
598        // Remove altitude requirement
599        criteria.setAltitudeRequired(false);
600        goodProviders = getProviders(criteria, enabledOnly);
601        if (!goodProviders.isEmpty()) {
602            return best(goodProviders).getName();
603        }
604
605        return null;
606    }
607
608    /**
609     * Registers the current activity to be notified periodically by
610     * the named provider.  Periodically, the supplied LocationListener will
611     * be called with the current Location or with status updates.
612     *
613     * <p> It may take a while to receive the most recent location. If
614     * an immediate location is required, applications may use the
615     * {@link #getLastKnownLocation(String)} method.
616     *
617     * <p> In case the provider is disabled by the user, updates will stop,
618     * and the {@link LocationListener#onProviderDisabled(String)}
619     * method will be called. As soon as the provider is enabled again,
620     * the {@link LocationListener#onProviderEnabled(String)} method will
621     * be called and location updates will start again.
622     *
623     * <p> The frequency of notification may be controlled using the
624     * minTime and minDistance parameters. If minTime is greater than 0,
625     * the LocationManager could potentially rest for minTime milliseconds
626     * between location updates to conserve power. If minDistance is greater than 0,
627     * a location will only be broadcasted if the device moves by minDistance meters.
628     * To obtain notifications as frequently as possible, set both parameters to 0.
629     *
630     * <p> Background services should be careful about setting a sufficiently high
631     * minTime so that the device doesn't consume too much power by keeping the
632     * GPS or wireless radios on all the time. In particular, values under 60000ms
633     * are not recommended.
634     *
635     * <p> The calling thread must be a {@link android.os.Looper} thread such as
636     * the main thread of the calling Activity.
637     *
638     * @param provider the name of the provider with which to register
639     * @param minTime the minimum time interval for notifications, in
640     * milliseconds. This field is only used as a hint to conserve power, and actual
641     * time between location updates may be greater or lesser than this value.
642     * @param minDistance the minimum distance interval for notifications,
643     * in meters
644     * @param listener a {#link LocationListener} whose
645     * {@link LocationListener#onLocationChanged} method will be called for
646     * each location update
647     *
648     * @throws IllegalArgumentException if provider is null or doesn't exist
649     * @throws IllegalArgumentException if listener is null
650     * @throws RuntimeException if the calling thread has no Looper
651     * @throws SecurityException if no suitable permission is present for the provider.
652     */
653    public void requestLocationUpdates(String provider,
654        long minTime, float minDistance, LocationListener listener) {
655        if (provider == null) {
656            throw new IllegalArgumentException("provider==null");
657        }
658        if (listener == null) {
659            throw new IllegalArgumentException("listener==null");
660        }
661        _requestLocationUpdates(provider, minTime, minDistance, listener, null);
662    }
663
664    /**
665     * Registers the current activity to be notified periodically by
666     * the named provider.  Periodically, the supplied LocationListener will
667     * be called with the current Location or with status updates.
668     *
669     * <p> It may take a while to receive the most recent location. If
670     * an immediate location is required, applications may use the
671     * {@link #getLastKnownLocation(String)} method.
672     *
673     * <p> In case the provider is disabled by the user, updates will stop,
674     * and the {@link LocationListener#onProviderDisabled(String)}
675     * method will be called. As soon as the provider is enabled again,
676     * the {@link LocationListener#onProviderEnabled(String)} method will
677     * be called and location updates will start again.
678     *
679     * <p> The frequency of notification may be controlled using the
680     * minTime and minDistance parameters. If minTime is greater than 0,
681     * the LocationManager could potentially rest for minTime milliseconds
682     * between location updates to conserve power. If minDistance is greater than 0,
683     * a location will only be broadcasted if the device moves by minDistance meters.
684     * To obtain notifications as frequently as possible, set both parameters to 0.
685     *
686     * <p> Background services should be careful about setting a sufficiently high
687     * minTime so that the device doesn't consume too much power by keeping the
688     * GPS or wireless radios on all the time. In particular, values under 60000ms
689     * are not recommended.
690     *
691     * <p> The supplied Looper is used to implement the callback mechanism.
692     *
693     * @param provider the name of the provider with which to register
694     * @param minTime the minimum time interval for notifications, in
695     * milliseconds. This field is only used as a hint to conserve power, and actual
696     * time between location updates may be greater or lesser than this value.
697     * @param minDistance the minimum distance interval for notifications,
698     * in meters
699     * @param listener a {#link LocationListener} whose
700     * {@link LocationListener#onLocationChanged} method will be called for
701     * each location update
702     * @param looper a Looper object whose message queue will be used to
703     * implement the callback mechanism.
704     *
705     * @throws IllegalArgumentException if provider is null or doesn't exist
706     * @throws IllegalArgumentException if listener is null
707     * @throws IllegalArgumentException if looper is null
708     * @throws SecurityException if no suitable permission is present for the provider.
709     */
710    public void requestLocationUpdates(String provider,
711        long minTime, float minDistance, LocationListener listener,
712        Looper looper) {
713        if (provider == null) {
714            throw new IllegalArgumentException("provider==null");
715        }
716        if (listener == null) {
717            throw new IllegalArgumentException("listener==null");
718        }
719        if (looper == null) {
720            throw new IllegalArgumentException("looper==null");
721        }
722        _requestLocationUpdates(provider, minTime, minDistance, listener, looper);
723    }
724
725    private void _requestLocationUpdates(String provider,
726        long minTime, float minDistance, LocationListener listener,
727        Looper looper) {
728        if (minTime < 0L) {
729            minTime = 0L;
730        }
731        if (minDistance < 0.0f) {
732            minDistance = 0.0f;
733        }
734
735        try {
736            synchronized (mListeners) {
737                ListenerTransport transport = mListeners.get(listener);
738                if (transport == null) {
739                    transport = new ListenerTransport(listener, looper);
740                }
741                mListeners.put(listener, transport);
742                mService.requestLocationUpdates(provider, minTime, minDistance, transport);
743            }
744        } catch (RemoteException ex) {
745            Log.e(TAG, "requestLocationUpdates: DeadObjectException", ex);
746        }
747    }
748
749    /**
750     * Registers the current activity to be notified periodically by
751     * the named provider.  Periodically, the supplied PendingIntent will
752     * be broadcast with the current Location or with status updates.
753     *
754     * <p> Location updates are sent with a key of KEY_LOCATION_CHANGED and a Location value.
755     *
756     * <p> It may take a while to receive the most recent location. If
757     * an immediate location is required, applications may use the
758     * {@link #getLastKnownLocation(String)} method.
759     *
760     * <p> The frequency of notification or new locations may be controlled using the
761     * minTime and minDistance parameters. If minTime is greater than 0,
762     * the LocationManager could potentially rest for minTime milliseconds
763     * between location updates to conserve power. If minDistance is greater than 0,
764     * a location will only be broadcast if the device moves by minDistance meters.
765     * To obtain notifications as frequently as possible, set both parameters to 0.
766     *
767     * <p> Background services should be careful about setting a sufficiently high
768     * minTime so that the device doesn't consume too much power by keeping the
769     * GPS or wireless radios on all the time. In particular, values under 60000ms
770     * are not recommended.
771     *
772     * <p> In case the provider is disabled by the user, updates will stop,
773     * and an intent will be sent with an extra with key KEY_PROVIDER_ENABLED and a boolean value
774     * of false.  If the provider is re-enabled, an intent will be sent with an
775     * extra with key KEY_PROVIDER_ENABLED and a boolean value of true and location updates will
776     * start again.
777     *
778     * <p> If the provider's status changes, an intent will be sent with an extra with key
779     * KEY_STATUS_CHANGED and an integer value indicating the new status.  Any extras associated
780     * with the status update will be sent as well.
781     *
782     * @param provider the name of the provider with which to register
783     * @param minTime the minimum time interval for notifications, in
784     * milliseconds. This field is only used as a hint to conserve power, and actual
785     * time between location updates may be greater or lesser than this value.
786     * @param minDistance the minimum distance interval for notifications,
787     * in meters
788     * @param intent a {#link PendingIntet} to be sent for each location update
789     *
790     * @throws IllegalArgumentException if provider is null or doesn't exist
791     * @throws IllegalArgumentException if intent is null
792     * @throws SecurityException if no suitable permission is present for the provider.
793     */
794    public void requestLocationUpdates(String provider,
795            long minTime, float minDistance, PendingIntent intent) {
796        if (provider == null) {
797            throw new IllegalArgumentException("provider==null");
798        }
799        if (intent == null) {
800            throw new IllegalArgumentException("intent==null");
801        }
802        _requestLocationUpdates(provider, minTime, minDistance, intent);
803    }
804
805    private void _requestLocationUpdates(String provider,
806            long minTime, float minDistance, PendingIntent intent) {
807        if (minTime < 0L) {
808            minTime = 0L;
809        }
810        if (minDistance < 0.0f) {
811            minDistance = 0.0f;
812        }
813
814        try {
815            mService.requestLocationUpdatesPI(provider, minTime, minDistance, intent);
816        } catch (RemoteException ex) {
817            Log.e(TAG, "requestLocationUpdates: RemoteException", ex);
818        }
819    }
820
821    /**
822     * Removes any current registration for location updates of the current activity
823     * with the given LocationListener.  Following this call, updates will no longer
824     * occur for this listener.
825     *
826     * @param listener {#link LocationListener} object that no longer needs location updates
827     * @throws IllegalArgumentException if listener is null
828     */
829    public void removeUpdates(LocationListener listener) {
830        if (listener == null) {
831            throw new IllegalArgumentException("listener==null");
832        }
833        if (false) {
834            Log.d(TAG, "removeUpdates: listener = " + listener);
835        }
836        try {
837            ListenerTransport transport = mListeners.remove(listener);
838            if (transport != null) {
839                mService.removeUpdates(transport);
840            }
841        } catch (RemoteException ex) {
842            Log.e(TAG, "removeUpdates: DeadObjectException", ex);
843        }
844    }
845
846    /**
847     * Removes any current registration for location updates of the current activity
848     * with the given PendingIntent.  Following this call, updates will no longer
849     * occur for this intent.
850     *
851     * @param intent {#link PendingIntent} object that no longer needs location updates
852     * @throws IllegalArgumentException if intent is null
853     */
854    public void removeUpdates(PendingIntent intent) {
855        if (intent == null) {
856            throw new IllegalArgumentException("intent==null");
857        }
858        if (false) {
859            Log.d(TAG, "removeUpdates: intent = " + intent);
860        }
861        try {
862            mService.removeUpdatesPI(intent);
863        } catch (RemoteException ex) {
864            Log.e(TAG, "removeUpdates: RemoteException", ex);
865        }
866    }
867
868    /**
869     * Sets a proximity alert for the location given by the position
870     * (latitude, longitude) and the given radius.  When the device
871     * detects that it has entered or exited the area surrounding the
872     * location, the given PendingIntent will be used to create an Intent
873     * to be fired.
874     *
875     * <p> The fired Intent will have a boolean extra added with key
876     * {@link #KEY_PROXIMITY_ENTERING}. If the value is true, the device is
877     * entering the proximity region; if false, it is exiting.
878     *
879     * <p> Due to the approximate nature of position estimation, if the
880     * device passes through the given area briefly, it is possible
881     * that no Intent will be fired.  Similarly, an Intent could be
882     * fired if the device passes very close to the given area but
883     * does not actually enter it.
884     *
885     * <p> After the number of milliseconds given by the expiration
886     * parameter, the location manager will delete this proximity
887     * alert and no longer monitor it.  A value of -1 indicates that
888     * there should be no expiration time.
889     *
890     * <p> In case the screen goes to sleep, checks for proximity alerts
891     * happen only once every 4 minutes. This conserves battery life by
892     * ensuring that the device isn't perpetually awake.
893     *
894     * <p> Internally, this method uses both {@link #NETWORK_PROVIDER}
895     * and {@link #GPS_PROVIDER}.
896     *
897     * @param latitude the latitude of the central point of the
898     * alert region
899     * @param longitude the longitude of the central point of the
900     * alert region
901     * @param radius the radius of the central point of the
902     * alert region, in meters
903     * @param expiration time for this proximity alert, in milliseconds,
904     * or -1 to indicate no expiration
905     * @param intent a PendingIntent that will be used to generate an Intent to
906     * fire when entry to or exit from the alert region is detected
907     *
908     * @throws SecurityException if no permission exists for the required
909     * providers.
910     */
911    public void addProximityAlert(double latitude, double longitude,
912        float radius, long expiration, PendingIntent intent) {
913        if (false) {
914            Log.d(TAG, "addProximityAlert: latitude = " + latitude +
915                ", longitude = " + longitude + ", radius = " + radius +
916                ", expiration = " + expiration +
917                ", intent = " + intent);
918        }
919        try {
920            mService.addProximityAlert(latitude, longitude, radius,
921                                       expiration, intent);
922        } catch (RemoteException ex) {
923            Log.e(TAG, "addProximityAlert: RemoteException", ex);
924        }
925    }
926
927    /**
928     * Removes the proximity alert with the given PendingIntent.
929     *
930     * @param intent the PendingIntent that no longer needs to be notified of
931     * proximity alerts
932     */
933    public void removeProximityAlert(PendingIntent intent) {
934        if (false) {
935            Log.d(TAG, "removeProximityAlert: intent = " + intent);
936        }
937        try {
938            mService.removeProximityAlert(intent);
939        } catch (RemoteException ex) {
940            Log.e(TAG, "removeProximityAlert: RemoteException", ex);
941        }
942    }
943
944    /**
945     * Returns the current enabled/disabled status of the given provider. If the
946     * user has enabled this provider in the Settings menu, true is returned
947     * otherwise false is returned
948     *
949     * @param provider the name of the provider
950     * @return true if the provider is enabled
951     *
952     * @throws SecurityException if no suitable permission is present for the provider.
953     * @throws IllegalArgumentException if provider is null or doesn't exist
954     */
955    public boolean isProviderEnabled(String provider) {
956        if (provider == null) {
957            throw new IllegalArgumentException("provider==null");
958        }
959        try {
960            return mService.isProviderEnabled(provider);
961        } catch (RemoteException ex) {
962            Log.e(TAG, "isProviderEnabled: RemoteException", ex);
963            return false;
964        }
965    }
966
967    /**
968     * Returns a Location indicating the data from the last known
969     * location fix obtained from the given provider.  This can be done
970     * without starting the provider.  Note that this location could
971     * be out-of-date, for example if the device was turned off and
972     * moved to another location.
973     *
974     * <p> If the provider is currently disabled, null is returned.
975     *
976     * @param provider the name of the provider
977     * @return the last known location for the provider, or null
978     *
979     * @throws SecurityException if no suitable permission is present for the provider.
980     * @throws IllegalArgumentException if provider is null or doesn't exist
981     */
982    public Location getLastKnownLocation(String provider) {
983        if (provider == null) {
984            throw new IllegalArgumentException("provider==null");
985        }
986        try {
987            return mService.getLastKnownLocation(provider);
988        } catch (RemoteException ex) {
989            Log.e(TAG, "getLastKnowLocation: RemoteException", ex);
990            return null;
991        }
992    }
993
994    // Mock provider support
995
996    /**
997     * Creates a mock location provider and adds it to the set of active providers.
998     *
999     * @param name the provider name
1000     * @param requiresNetwork
1001     * @param requiresSatellite
1002     * @param requiresCell
1003     * @param hasMonetaryCost
1004     * @param supportsAltitude
1005     * @param supportsSpeed
1006     * @param supportsBearing
1007     * @param powerRequirement
1008     * @param accuracy
1009     *
1010     * @throws SecurityException if the ACCESS_MOCK_LOCATION permission is not present
1011     * or the {@link android.provider.Settings.Secure#ALLOW_MOCK_LOCATION
1012     * Settings.Secure.ALLOW_MOCK_LOCATION} system setting is not enabled
1013     * @throws IllegalArgumentException if a provider with the given name already exists
1014     */
1015    public void addTestProvider(String name, boolean requiresNetwork, boolean requiresSatellite,
1016        boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude,
1017        boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy) {
1018        try {
1019            mService.addTestProvider(name, requiresNetwork, requiresSatellite, requiresCell,
1020                hasMonetaryCost, supportsAltitude, supportsSpeed, supportsBearing, powerRequirement,
1021                accuracy);
1022        } catch (RemoteException ex) {
1023            Log.e(TAG, "addTestProvider: RemoteException", ex);
1024        }
1025    }
1026
1027    /**
1028     * Removes the mock location provider with the given name.
1029     *
1030     * @param provider the provider name
1031     *
1032     * @throws SecurityException if the ACCESS_MOCK_LOCATION permission is not present
1033     * or the {@link android.provider.Settings.Secure#ALLOW_MOCK_LOCATION
1034     * Settings.Secure.ALLOW_MOCK_LOCATION}} system setting is not enabled
1035     * @throws IllegalArgumentException if no provider with the given name exists
1036     */
1037    public void removeTestProvider(String provider) {
1038        try {
1039            mService.removeTestProvider(provider);
1040        } catch (RemoteException ex) {
1041            Log.e(TAG, "removeTestProvider: RemoteException", ex);
1042        }
1043    }
1044
1045    /**
1046     * Sets a mock location for the given provider.  This location will be used in place
1047     * of any actual location from the provider.
1048     *
1049     * @param provider the provider name
1050     * @param loc the mock location
1051     *
1052     * @throws SecurityException if the ACCESS_MOCK_LOCATION permission is not present
1053     * or the {@link android.provider.Settings.Secure#ALLOW_MOCK_LOCATION
1054     * Settings.Secure.ALLOW_MOCK_LOCATION}} system setting is not enabled
1055     * @throws IllegalArgumentException if no provider with the given name exists
1056     */
1057    public void setTestProviderLocation(String provider, Location loc) {
1058        try {
1059            mService.setTestProviderLocation(provider, loc);
1060        } catch (RemoteException ex) {
1061            Log.e(TAG, "setTestProviderLocation: RemoteException", ex);
1062        }
1063    }
1064
1065    /**
1066     * Removes any mock location associated with the given provider.
1067     *
1068     * @param provider the provider name
1069     *
1070     * @throws SecurityException if the ACCESS_MOCK_LOCATION permission is not present
1071     * or the {@link android.provider.Settings.Secure#ALLOW_MOCK_LOCATION
1072     * Settings.Secure.ALLOW_MOCK_LOCATION}} system setting is not enabled
1073     * @throws IllegalArgumentException if no provider with the given name exists
1074     */
1075    public void clearTestProviderLocation(String provider) {
1076        try {
1077            mService.clearTestProviderLocation(provider);
1078        } catch (RemoteException ex) {
1079            Log.e(TAG, "clearTestProviderLocation: RemoteException", ex);
1080        }
1081    }
1082
1083    /**
1084     * Sets a mock enabled value for the given provider.  This value will be used in place
1085     * of any actual value from the provider.
1086     *
1087     * @param provider the provider name
1088     * @param enabled the mock enabled value
1089     *
1090     * @throws SecurityException if the ACCESS_MOCK_LOCATION permission is not present
1091     * or the {@link android.provider.Settings.Secure#ALLOW_MOCK_LOCATION
1092     * Settings.Secure.ALLOW_MOCK_LOCATION}} system setting is not enabled
1093     * @throws IllegalArgumentException if no provider with the given name exists
1094     */
1095    public void setTestProviderEnabled(String provider, boolean enabled) {
1096        try {
1097            mService.setTestProviderEnabled(provider, enabled);
1098        } catch (RemoteException ex) {
1099            Log.e(TAG, "setTestProviderEnabled: RemoteException", ex);
1100        }
1101    }
1102
1103    /**
1104     * Removes any mock enabled value associated with the given provider.
1105     *
1106     * @param provider the provider name
1107     *
1108     * @throws SecurityException if the ACCESS_MOCK_LOCATION permission is not present
1109     * or the {@link android.provider.Settings.Secure#ALLOW_MOCK_LOCATION
1110     * Settings.Secure.ALLOW_MOCK_LOCATION}} system setting is not enabled
1111     * @throws IllegalArgumentException if no provider with the given name exists
1112     */
1113    public void clearTestProviderEnabled(String provider) {
1114        try {
1115            mService.clearTestProviderEnabled(provider);
1116        } catch (RemoteException ex) {
1117            Log.e(TAG, "clearTestProviderEnabled: RemoteException", ex);
1118        }
1119
1120    }
1121
1122    /**
1123     * Sets mock status values for the given provider.  These values will be used in place
1124     * of any actual values from the provider.
1125     *
1126     * @param provider the provider name
1127     * @param status the mock status
1128     * @param extras a Bundle containing mock extras
1129     * @param updateTime the mock update time
1130     *
1131     * @throws SecurityException if the ACCESS_MOCK_LOCATION permission is not present
1132     * or the {@link android.provider.Settings.Secure#ALLOW_MOCK_LOCATION
1133     * Settings.Secure.ALLOW_MOCK_LOCATION}} system setting is not enabled
1134     * @throws IllegalArgumentException if no provider with the given name exists
1135     */
1136    public void setTestProviderStatus(String provider, int status, Bundle extras, long updateTime) {
1137        try {
1138            mService.setTestProviderStatus(provider, status, extras, updateTime);
1139        } catch (RemoteException ex) {
1140            Log.e(TAG, "setTestProviderStatus: RemoteException", ex);
1141        }
1142    }
1143
1144    /**
1145     * Removes any mock status values associated with the given provider.
1146     *
1147     * @param provider the provider name
1148     *
1149     * @throws SecurityException if the ACCESS_MOCK_LOCATION permission is not present
1150     * or the {@link android.provider.Settings.Secure#ALLOW_MOCK_LOCATION
1151     * Settings.Secure.ALLOW_MOCK_LOCATION}} system setting is not enabled
1152     * @throws IllegalArgumentException if no provider with the given name exists
1153     */
1154    public void clearTestProviderStatus(String provider) {
1155        try {
1156            mService.clearTestProviderStatus(provider);
1157        } catch (RemoteException ex) {
1158            Log.e(TAG, "clearTestProviderStatus: RemoteException", ex);
1159        }
1160    }
1161
1162    // GPS-specific support
1163
1164    // This class is used to send GPS status events to the client's main thread.
1165    private class GpsStatusListenerTransport extends IGpsStatusListener.Stub {
1166
1167        private final GpsStatus.Listener mListener;
1168        private final GpsStatus.NmeaListener mNmeaListener;
1169
1170        // This must not equal any of the GpsStatus event IDs
1171        private static final int NMEA_RECEIVED = 1000;
1172
1173        private class Nmea {
1174            long mTimestamp;
1175            String mNmea;
1176
1177            Nmea(long timestamp, String nmea) {
1178                mTimestamp = timestamp;
1179                mNmea = nmea;
1180            }
1181        }
1182        private ArrayList<Nmea> mNmeaBuffer;
1183
1184        GpsStatusListenerTransport(GpsStatus.Listener listener) {
1185            mListener = listener;
1186            mNmeaListener = null;
1187        }
1188
1189        GpsStatusListenerTransport(GpsStatus.NmeaListener listener) {
1190            mNmeaListener = listener;
1191            mListener = null;
1192            mNmeaBuffer = new ArrayList<Nmea>();
1193        }
1194
1195        public void onGpsStarted() {
1196            if (mListener != null) {
1197                Message msg = Message.obtain();
1198                msg.what = GpsStatus.GPS_EVENT_STARTED;
1199                mGpsHandler.sendMessage(msg);
1200            }
1201        }
1202
1203        public void onGpsStopped() {
1204            if (mListener != null) {
1205                Message msg = Message.obtain();
1206                msg.what = GpsStatus.GPS_EVENT_STOPPED;
1207                mGpsHandler.sendMessage(msg);
1208            }
1209        }
1210
1211        public void onFirstFix(int ttff) {
1212            if (mListener != null) {
1213                mGpsStatus.setTimeToFirstFix(ttff);
1214                Message msg = Message.obtain();
1215                msg.what = GpsStatus.GPS_EVENT_FIRST_FIX;
1216                mGpsHandler.sendMessage(msg);
1217            }
1218        }
1219
1220        public void onSvStatusChanged(int svCount, int[] prns, float[] snrs,
1221                float[] elevations, float[] azimuths, int ephemerisMask,
1222                int almanacMask, int usedInFixMask) {
1223            if (mListener != null) {
1224                mGpsStatus.setStatus(svCount, prns, snrs, elevations, azimuths,
1225                        ephemerisMask, almanacMask, usedInFixMask);
1226
1227                Message msg = Message.obtain();
1228                msg.what = GpsStatus.GPS_EVENT_SATELLITE_STATUS;
1229                // remove any SV status messages already in the queue
1230                mGpsHandler.removeMessages(GpsStatus.GPS_EVENT_SATELLITE_STATUS);
1231                mGpsHandler.sendMessage(msg);
1232            }
1233        }
1234
1235        public void onNmeaReceived(long timestamp, String nmea) {
1236            if (mNmeaListener != null) {
1237                synchronized (mNmeaBuffer) {
1238                    mNmeaBuffer.add(new Nmea(timestamp, nmea));
1239                }
1240                Message msg = Message.obtain();
1241                msg.what = NMEA_RECEIVED;
1242                // remove any NMEA_RECEIVED messages already in the queue
1243                mGpsHandler.removeMessages(NMEA_RECEIVED);
1244                mGpsHandler.sendMessage(msg);
1245            }
1246        }
1247
1248        private final Handler mGpsHandler = new Handler() {
1249            @Override
1250            public void handleMessage(Message msg) {
1251                if (msg.what == NMEA_RECEIVED) {
1252                    synchronized (mNmeaBuffer) {
1253                        int length = mNmeaBuffer.size();
1254                        for (int i = 0; i < length; i++) {
1255                            Nmea nmea = mNmeaBuffer.get(i);
1256                            mNmeaListener.onNmeaReceived(nmea.mTimestamp, nmea.mNmea);
1257                        }
1258                        mNmeaBuffer.clear();
1259                    }
1260                } else {
1261                    // synchronize on mGpsStatus to ensure the data is copied atomically.
1262                    synchronized(mGpsStatus) {
1263                        mListener.onGpsStatusChanged(msg.what);
1264                    }
1265                }
1266            }
1267        };
1268    }
1269
1270    /**
1271     * Adds a GPS status listener.
1272     *
1273     * @param listener GPS status listener object to register
1274     *
1275     * @return true if the listener was successfully added
1276     *
1277     * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
1278     */
1279    public boolean addGpsStatusListener(GpsStatus.Listener listener) {
1280        boolean result;
1281
1282        if (mGpsStatusListeners.get(listener) != null) {
1283            // listener is already registered
1284            return true;
1285        }
1286        try {
1287            GpsStatusListenerTransport transport = new GpsStatusListenerTransport(listener);
1288            result = mService.addGpsStatusListener(transport);
1289            if (result) {
1290                mGpsStatusListeners.put(listener, transport);
1291            }
1292        } catch (RemoteException e) {
1293            Log.e(TAG, "RemoteException in registerGpsStatusListener: ", e);
1294            result = false;
1295        }
1296
1297        return result;
1298    }
1299
1300    /**
1301     * Removes a GPS status listener.
1302     *
1303     * @param listener GPS status listener object to remove
1304     */
1305    public void removeGpsStatusListener(GpsStatus.Listener listener) {
1306        try {
1307            GpsStatusListenerTransport transport = mGpsStatusListeners.remove(listener);
1308            if (transport != null) {
1309                mService.removeGpsStatusListener(transport);
1310            }
1311        } catch (RemoteException e) {
1312            Log.e(TAG, "RemoteException in unregisterGpsStatusListener: ", e);
1313        }
1314    }
1315
1316    /**
1317     * Adds an NMEA listener.
1318     *
1319     * @param listener a {#link GpsStatus.NmeaListener} object to register
1320     *
1321     * @return true if the listener was successfully added
1322     *
1323     * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
1324     */
1325    public boolean addNmeaListener(GpsStatus.NmeaListener listener) {
1326        boolean result;
1327
1328        if (mNmeaListeners.get(listener) != null) {
1329            // listener is already registered
1330            return true;
1331        }
1332        try {
1333            GpsStatusListenerTransport transport = new GpsStatusListenerTransport(listener);
1334            result = mService.addGpsStatusListener(transport);
1335            if (result) {
1336                mNmeaListeners.put(listener, transport);
1337            }
1338        } catch (RemoteException e) {
1339            Log.e(TAG, "RemoteException in registerGpsStatusListener: ", e);
1340            result = false;
1341        }
1342
1343        return result;
1344    }
1345
1346    /**
1347     * Removes an NMEA listener.
1348     *
1349     * @param listener a {#link GpsStatus.NmeaListener} object to remove
1350     */
1351    public void removeNmeaListener(GpsStatus.NmeaListener listener) {
1352        try {
1353            GpsStatusListenerTransport transport = mNmeaListeners.remove(listener);
1354            if (transport != null) {
1355                mService.removeGpsStatusListener(transport);
1356            }
1357        } catch (RemoteException e) {
1358            Log.e(TAG, "RemoteException in unregisterGpsStatusListener: ", e);
1359        }
1360    }
1361
1362     /**
1363     * Retrieves information about the current status of the GPS engine.
1364     * This should only be called from the {@link GpsStatus.Listener#onGpsStatusChanged}
1365     * callback to ensure that the data is copied atomically.
1366     *
1367     * The caller may either pass in a {@link GpsStatus} object to set with the latest
1368     * status information, or pass null to create a new {@link GpsStatus} object.
1369     *
1370     * @param status object containing GPS status details, or null.
1371     * @return status object containing updated GPS status.
1372     */
1373    public GpsStatus getGpsStatus(GpsStatus status) {
1374        if (status == null) {
1375            status = new GpsStatus();
1376       }
1377       status.setStatus(mGpsStatus);
1378       return status;
1379    }
1380
1381    /**
1382     * Sends additional commands to a location provider.
1383     * Can be used to support provider specific extensions to the Location Manager API
1384     *
1385     * @param provider name of the location provider.
1386     * @param command name of the command to send to the provider.
1387     * @param extras optional arguments for the command (or null).
1388     * The provider may optionally fill the extras Bundle with results from the command.
1389     *
1390     * @return true if the command succeeds.
1391     */
1392    public boolean sendExtraCommand(String provider, String command, Bundle extras) {
1393        try {
1394            return mService.sendExtraCommand(provider, command, extras);
1395        } catch (RemoteException e) {
1396            Log.e(TAG, "RemoteException in sendExtraCommand: ", e);
1397            return false;
1398        }
1399    }
1400
1401    /**
1402     * Used by NetInitiatedActivity to report user response
1403     * for network initiated GPS fix requests.
1404     *
1405     * {@hide}
1406     */
1407    public boolean sendNiResponse(int notifId, int userResponse) {
1408    	try {
1409            return mService.sendNiResponse(notifId, userResponse);
1410        } catch (RemoteException e) {
1411            Log.e(TAG, "RemoteException in sendNiResponse: ", e);
1412            return false;
1413        }
1414    }
1415
1416}
1417