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 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.util.Log; 14 15 import org.chromium.base.ThreadUtils; 16 import org.chromium.base.VisibleForTesting; 17 18 import java.util.List; 19 20 /** 21 * Factory to create a LocationProvider to allow us to inject 22 * a mock for tests. 23 */ 24 public class LocationProviderFactory { 25 private static LocationProviderFactory.LocationProvider sProviderImpl; 26 27 /** 28 * LocationProviderFactory.get() returns an instance of this interface. 29 */ 30 public interface LocationProvider { start(boolean gpsEnabled)31 public void start(boolean gpsEnabled); stop()32 public void stop(); isRunning()33 public boolean isRunning(); 34 } 35 LocationProviderFactory()36 private LocationProviderFactory() { 37 } 38 39 @VisibleForTesting setLocationProviderImpl(LocationProviderFactory.LocationProvider provider)40 public static void setLocationProviderImpl(LocationProviderFactory.LocationProvider provider) { 41 assert sProviderImpl == null; 42 sProviderImpl = provider; 43 } 44 get(Context context)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 LocationProviderImpl(Context context)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 start(boolean gpsEnabled)76 public void start(boolean gpsEnabled) { 77 unregisterFromLocationUpdates(); 78 registerForLocationUpdates(gpsEnabled); 79 } 80 81 /** 82 * Stop listening for location updates. 83 */ 84 @Override stop()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 isRunning()93 public boolean isRunning() { 94 return mIsRunning; 95 } 96 97 @Override onLocationChanged(Location location)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 updateNewLocation(Location location)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 onStatusChanged(String provider, int status, Bundle extras)118 public void onStatusChanged(String provider, int status, Bundle extras) { 119 } 120 121 @Override onProviderEnabled(String provider)122 public void onProviderEnabled(String provider) { 123 } 124 125 @Override onProviderDisabled(String provider)126 public void onProviderDisabled(String provider) { 127 } 128 ensureLocationManagerCreated()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 */ registerForLocationUpdates(boolean isGpsEnabled)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 */ unregisterFromLocationUpdates()166 private void unregisterFromLocationUpdates() { 167 if (mIsRunning) { 168 mIsRunning = false; 169 mLocationManager.removeUpdates(this); 170 } 171 } 172 usePassiveOneShotLocation()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 */ isOnlyPassiveLocationProviderEnabled()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