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 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.os.Looper; 14import android.util.Log; 15 16import org.chromium.base.ActivityStatus; 17import org.chromium.base.CalledByNative; 18import org.chromium.base.ThreadUtils; 19 20import java.util.List; 21import 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 */ 30class 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 ThreadUtils.getUiThreadLooper()); 156 if (mIsGpsEnabled) { 157 criteria.setAccuracy(Criteria.ACCURACY_FINE); 158 mLocationManager.requestLocationUpdates(0, 0, criteria, this, 159 ThreadUtils.getUiThreadLooper()); 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 ThreadUtils.runningOnUiThread(); 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} 270