LocationProviderFactory.java revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.content.browser;
6
7import android.content.Context;
8import android.location.Criteria;
9import android.location.Location;
10import android.location.LocationListener;
11import android.location.LocationManager;
12import android.os.Bundle;
13import android.util.Log;
14
15import org.chromium.base.ThreadUtils;
16import org.chromium.base.VisibleForTesting;
17
18import java.util.List;
19
20/**
21 * Factory to create a LocationProvider to allow us to inject
22 * a mock for tests.
23 */
24public class LocationProviderFactory {
25    private static LocationProviderFactory.LocationProvider sProviderImpl;
26
27    /**
28     * LocationProviderFactory.get() returns an instance of this interface.
29     */
30    public interface LocationProvider {
31        public void start(boolean gpsEnabled);
32        public void stop();
33        public boolean isRunning();
34    }
35
36    private LocationProviderFactory() {
37    }
38
39    @VisibleForTesting
40    public static void setLocationProviderImpl(LocationProviderFactory.LocationProvider provider) {
41        assert sProviderImpl == null;
42        sProviderImpl = provider;
43    }
44
45    public static LocationProvider get(Context context) {
46        if (sProviderImpl == null) {
47            sProviderImpl = new LocationProviderImpl(context);
48        }
49        return sProviderImpl;
50    }
51
52    /**
53     * This is the core of android location provider. It is a separate class for clarity
54     * so that it can manage all processing completely in the UI thread. The container class
55     * ensures that the start/stop calls into this class are done in the UI thread.
56     */
57    private static class LocationProviderImpl
58            implements LocationListener, LocationProviderFactory.LocationProvider {
59
60        // Log tag
61        private static final String TAG = "LocationProvider";
62
63        private Context mContext;
64        private LocationManager mLocationManager;
65        private boolean mIsRunning;
66
67        LocationProviderImpl(Context context) {
68            mContext = context;
69        }
70
71        /**
72         * Start listening for location updates.
73         * @param gpsEnabled Whether or not we're interested in high accuracy GPS.
74         */
75        @Override
76        public void start(boolean gpsEnabled) {
77            unregisterFromLocationUpdates();
78            registerForLocationUpdates(gpsEnabled);
79        }
80
81        /**
82         * Stop listening for location updates.
83         */
84        @Override
85        public void stop() {
86            unregisterFromLocationUpdates();
87        }
88
89        /**
90         * Returns true if we are currently listening for location updates, false if not.
91         */
92        @Override
93        public boolean isRunning() {
94            return mIsRunning;
95        }
96
97        @Override
98        public void onLocationChanged(Location location) {
99            // Callbacks from the system location service are queued to this thread, so it's
100            // possible that we receive callbacks after unregistering. At this point, the
101            // native object will no longer exist.
102            if (mIsRunning) {
103                updateNewLocation(location);
104            }
105        }
106
107        private void updateNewLocation(Location location) {
108            LocationProviderAdapter.newLocationAvailable(
109                    location.getLatitude(), location.getLongitude(),
110                    location.getTime() / 1000.0,
111                    location.hasAltitude(), location.getAltitude(),
112                    location.hasAccuracy(), location.getAccuracy(),
113                    location.hasBearing(), location.getBearing(),
114                    location.hasSpeed(), location.getSpeed());
115        }
116
117        @Override
118        public void onStatusChanged(String provider, int status, Bundle extras) {
119        }
120
121        @Override
122        public void onProviderEnabled(String provider) {
123        }
124
125        @Override
126        public void onProviderDisabled(String provider) {
127        }
128
129        private void ensureLocationManagerCreated() {
130            if (mLocationManager != null) return;
131            mLocationManager = (LocationManager) mContext.getSystemService(
132                    Context.LOCATION_SERVICE);
133            if (mLocationManager == null) {
134                Log.e(TAG, "Could not get location manager.");
135            }
136        }
137
138        /**
139         * Registers this object with the location service.
140         */
141        private void registerForLocationUpdates(boolean isGpsEnabled) {
142            ensureLocationManagerCreated();
143            if (usePassiveOneShotLocation()) return;
144
145            assert !mIsRunning;
146            mIsRunning = true;
147
148            // We're running on the main thread. The C++ side is responsible to
149            // bounce notifications to the Geolocation thread as they arrive in the mainLooper.
150            try {
151                Criteria criteria = new Criteria();
152                if (isGpsEnabled) criteria.setAccuracy(Criteria.ACCURACY_FINE);
153                mLocationManager.requestLocationUpdates(0, 0, criteria, this,
154                        ThreadUtils.getUiThreadLooper());
155            } catch (SecurityException e) {
156                Log.e(TAG, "Caught security exception registering for location updates from " +
157                    "system. This should only happen in DumpRenderTree.");
158            } catch (IllegalArgumentException e) {
159                Log.e(TAG, "Caught IllegalArgumentException registering for location updates.");
160            }
161        }
162
163        /**
164         * Unregisters this object from the location service.
165         */
166        private void unregisterFromLocationUpdates() {
167            if (mIsRunning) {
168                mIsRunning = false;
169                mLocationManager.removeUpdates(this);
170            }
171        }
172
173        private boolean usePassiveOneShotLocation() {
174            if (!isOnlyPassiveLocationProviderEnabled()) return false;
175
176            // Do not request a location update if the only available location provider is
177            // the passive one. Make use of the last known location and call
178            // onLocationChanged directly.
179            final Location location = mLocationManager.getLastKnownLocation(
180                    LocationManager.PASSIVE_PROVIDER);
181            if (location != null) {
182                ThreadUtils.runOnUiThread(new Runnable() {
183                    @Override
184                    public void run() {
185                        updateNewLocation(location);
186                    }
187                });
188            }
189            return true;
190        }
191
192        /*
193         * Checks if the passive location provider is the only provider available
194         * in the system.
195         */
196        private boolean isOnlyPassiveLocationProviderEnabled() {
197            List<String> providers = mLocationManager.getProviders(true);
198            return providers != null && providers.size() == 1
199                    && providers.get(0).equals(LocationManager.PASSIVE_PROVIDER);
200        }
201    }
202}
203
204
205