1 | // Copyright (c) 2012 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 | |
5 | package org.chromium.content.browser; |
6 | |
7 | import android.content.Context; |
8 | import android.location.Criteria; |
9 | import android.location.Location; |
10 | import android.location.LocationListener; |
11 | import android.location.LocationManager; |
12 | import android.os.Bundle; |
13 | import android.os.Looper; |
14 | import android.util.Log; |
15 | |
16 | import org.chromium.base.ActivityStatus; |
17 | import org.chromium.base.CalledByNative; |
18 | import org.chromium.base.ThreadUtils; |
19 | |
20 | import java.util.List; |
21 | import java.util.concurrent.FutureTask; |
22 | |
23 | /** |
24 | * Implements the Java side of LocationProviderAndroid. |
25 | * Delegates all real functionality to the inner class. |
26 | * See detailed documentation on |
27 | * content/browser/geolocation/android_location_api_adapter.h. |
28 | * Based on android.webkit.GeolocationService.java |
29 | */ |
30 | class LocationProvider { |
31 | |
32 | // Log tag |
33 | private static final String TAG = "LocationProvider"; |
34 | |
35 | /** |
36 | * This is the core of android location provider. It is a separate class for clarity |
37 | * so that it can manage all processing completely in the UI thread. The container class |
38 | * ensures that the start/stop calls into this class are done in the UI thread. |
39 | */ |
40 | private static class LocationProviderImpl |
41 | implements LocationListener, ActivityStatus.StateListener { |
42 | |
43 | private Context mContext; |
44 | private LocationManager mLocationManager; |
45 | private boolean mIsRunning; |
46 | private boolean mShouldRunAfterActivityResume; |
47 | private boolean mIsGpsEnabled; |
48 | |
49 | LocationProviderImpl(Context context) { |
50 | mContext = context; |
51 | } |
52 | |
53 | @Override |
54 | public void onActivityStateChange(int state) { |
55 | if (state == ActivityStatus.PAUSED) { |
56 | mShouldRunAfterActivityResume = mIsRunning; |
57 | unregisterFromLocationUpdates(); |
58 | } else if (state == ActivityStatus.RESUMED) { |
59 | assert !mIsRunning; |
60 | if (mShouldRunAfterActivityResume) { |
61 | registerForLocationUpdates(); |
62 | } |
63 | } |
64 | } |
65 | |
66 | /** |
67 | * Start listening for location updates. |
68 | * @param gpsEnabled Whether or not we're interested in high accuracy GPS. |
69 | */ |
70 | private void start(boolean gpsEnabled) { |
71 | if (!mIsRunning && !mShouldRunAfterActivityResume) { |
72 | // Currently idle so start listening to activity status changes. |
73 | ActivityStatus.registerStateListener(this); |
74 | } |
75 | mIsGpsEnabled = gpsEnabled; |
76 | if (ActivityStatus.isPaused()) { |
77 | mShouldRunAfterActivityResume = true; |
78 | } else { |
79 | unregisterFromLocationUpdates(); |
80 | registerForLocationUpdates(); |
81 | } |
82 | } |
83 | |
84 | /** |
85 | * Stop listening for location updates. |
86 | */ |
87 | private void stop() { |
88 | unregisterFromLocationUpdates(); |
89 | ActivityStatus.unregisterStateListener(this); |
90 | mShouldRunAfterActivityResume = false; |
91 | } |
92 | |
93 | /** |
94 | * Returns true if we are currently listening for location updates, false if not. |
95 | */ |
96 | private boolean isRunning() { |
97 | return mIsRunning; |
98 | } |
99 | |
100 | @Override |
101 | public void onLocationChanged(Location location) { |
102 | // Callbacks from the system location sevice are queued to this thread, so it's |
103 | // possible that we receive callbacks after unregistering. At this point, the |
104 | // native object will no longer exist. |
105 | if (mIsRunning) { |
106 | updateNewLocation(location); |
107 | } |
108 | } |
109 | |
110 | private void updateNewLocation(Location location) { |
111 | nativeNewLocationAvailable(location.getLatitude(), location.getLongitude(), |
112 | location.getTime() / 1000.0, |
113 | location.hasAltitude(), location.getAltitude(), |
114 | location.hasAccuracy(), location.getAccuracy(), |
115 | location.hasBearing(), location.getBearing(), |
116 | location.hasSpeed(), location.getSpeed()); |
117 | } |
118 | |
119 | @Override |
120 | public void onStatusChanged(String provider, int status, Bundle extras) { |
121 | } |
122 | |
123 | @Override |
124 | public void onProviderEnabled(String provider) { |
125 | } |
126 | |
127 | @Override |
128 | public void onProviderDisabled(String provider) { |
129 | } |
130 | |
131 | private void ensureLocationManagerCreated() { |
132 | if (mLocationManager != null) return; |
133 | mLocationManager = (LocationManager) mContext.getSystemService( |
134 | Context.LOCATION_SERVICE); |
135 | if (mLocationManager == null) { |
136 | Log.e(TAG, "Could not get location manager."); |
137 | } |
138 | } |
139 | |
140 | /** |
141 | * Registers this object with the location service. |
142 | */ |
143 | private void registerForLocationUpdates() { |
144 | ensureLocationManagerCreated(); |
145 | if (usePassiveOneShotLocation()) return; |
146 | |
147 | assert !mIsRunning; |
148 | mIsRunning = true; |
149 | |
150 | // We're running on the main thread. The C++ side is responsible to |
151 | // bounce notifications to the Geolocation thread as they arrive in the mainLooper. |
152 | try { |
153 | Criteria criteria = new Criteria(); |
154 | mLocationManager.requestLocationUpdates(0, 0, criteria, this, |
155 | Looper.getMainLooper()); |
156 | if (mIsGpsEnabled) { |
157 | criteria.setAccuracy(Criteria.ACCURACY_FINE); |
158 | mLocationManager.requestLocationUpdates(0, 0, criteria, this, |
159 | Looper.getMainLooper()); |
160 | } |
161 | } catch(SecurityException e) { |
162 | Log.e(TAG, "Caught security exception registering for location updates from " + |
163 | "system. This should only happen in DumpRenderTree."); |
164 | } catch(IllegalArgumentException e) { |
165 | Log.e(TAG, "Caught IllegalArgumentException registering for location updates."); |
166 | } |
167 | } |
168 | |
169 | /** |
170 | * Unregisters this object from the location service. |
171 | */ |
172 | private void unregisterFromLocationUpdates() { |
173 | if (mIsRunning) { |
174 | mIsRunning = false; |
175 | mLocationManager.removeUpdates(this); |
176 | } |
177 | } |
178 | |
179 | private boolean usePassiveOneShotLocation() { |
180 | if (!isOnlyPassiveLocationProviderEnabled()) return false; |
181 | |
182 | // Do not request a location update if the only available location provider is |
183 | // the passive one. Make use of the last known location and call |
184 | // onLocationChanged directly. |
185 | final Location location = mLocationManager.getLastKnownLocation( |
186 | LocationManager.PASSIVE_PROVIDER); |
187 | if (location != null) { |
188 | ThreadUtils.runOnUiThread(new Runnable() { |
189 | @Override |
190 | public void run() { |
191 | updateNewLocation(location); |
192 | } |
193 | }); |
194 | } |
195 | return true; |
196 | } |
197 | |
198 | /* |
199 | * Checks if the passive location provider is the only provider available |
200 | * in the system. |
201 | */ |
202 | private boolean isOnlyPassiveLocationProviderEnabled() { |
203 | List<String> providers = mLocationManager.getProviders(true); |
204 | return providers != null && providers.size() == 1 |
205 | && providers.get(0).equals(LocationManager.PASSIVE_PROVIDER); |
206 | } |
207 | } |
208 | |
209 | // Delegate handling the real work in the main thread. |
210 | private LocationProviderImpl mImpl; |
211 | |
212 | private LocationProvider(Context context) { |
213 | mImpl = new LocationProviderImpl(context); |
214 | } |
215 | |
216 | @CalledByNative |
217 | static LocationProvider create(Context context) { |
218 | return new LocationProvider(context); |
219 | } |
220 | |
221 | /** |
222 | * Start listening for location updates until we're told to quit. May be |
223 | * called in any thread. |
224 | * @param gpsEnabled Whether or not we're interested in high accuracy GPS. |
225 | */ |
226 | @CalledByNative |
227 | public boolean start(final boolean gpsEnabled) { |
228 | FutureTask<Void> task = new FutureTask<Void>(new Runnable() { |
229 | @Override |
230 | public void run() { |
231 | mImpl.start(gpsEnabled); |
232 | } |
233 | }, null); |
234 | ThreadUtils.runOnUiThread(task); |
235 | return true; |
236 | } |
237 | |
238 | /** |
239 | * Stop listening for location updates. May be called in any thread. |
240 | */ |
241 | @CalledByNative |
242 | public void stop() { |
243 | FutureTask<Void> task = new FutureTask<Void>(new Runnable() { |
244 | @Override |
245 | public void run() { |
246 | mImpl.stop(); |
247 | } |
248 | }, null); |
249 | ThreadUtils.runOnUiThread(task); |
250 | } |
251 | |
252 | /** |
253 | * Returns true if we are currently listening for location updates, false if not. |
254 | * Must be called only in the UI thread. |
255 | */ |
256 | public boolean isRunning() { |
257 | assert Looper.myLooper() == Looper.getMainLooper(); |
258 | return mImpl.isRunning(); |
259 | } |
260 | |
261 | // Native functions |
262 | public static native void nativeNewLocationAvailable( |
263 | double latitude, double longitude, double timeStamp, |
264 | boolean hasAltitude, double altitude, |
265 | boolean hasAccuracy, double accuracy, |
266 | boolean hasHeading, double heading, |
267 | boolean hasSpeed, double speed); |
268 | public static native void nativeNewErrorAvailable(String message); |
269 | } |