| // Copyright 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.content.browser; |
| |
| import android.content.Context; |
| import android.location.Criteria; |
| import android.location.Location; |
| import android.location.LocationListener; |
| import android.location.LocationManager; |
| import android.os.Bundle; |
| import android.util.Log; |
| |
| import org.chromium.base.ActivityStatus; |
| import org.chromium.base.CalledByNative; |
| import org.chromium.base.ThreadUtils; |
| |
| import java.util.List; |
| import java.util.concurrent.FutureTask; |
| |
| /** |
| * Implements the Java side of LocationProviderAndroid. |
| * Delegates all real functionality to the inner class. |
| * See detailed documentation on |
| * content/browser/geolocation/android_location_api_adapter.h. |
| * Based on android.webkit.GeolocationService.java |
| */ |
| class LocationProvider { |
| |
| // Log tag |
| private static final String TAG = "LocationProvider"; |
| |
| /** |
| * This is the core of android location provider. It is a separate class for clarity |
| * so that it can manage all processing completely in the UI thread. The container class |
| * ensures that the start/stop calls into this class are done in the UI thread. |
| */ |
| private static class LocationProviderImpl |
| implements LocationListener, ActivityStatus.StateListener { |
| |
| private Context mContext; |
| private LocationManager mLocationManager; |
| private boolean mIsRunning; |
| private boolean mShouldRunAfterActivityResume; |
| private boolean mIsGpsEnabled; |
| |
| LocationProviderImpl(Context context) { |
| mContext = context; |
| } |
| |
| @Override |
| public void onActivityStateChange(int state) { |
| if (state == ActivityStatus.PAUSED) { |
| mShouldRunAfterActivityResume |= mIsRunning; |
| unregisterFromLocationUpdates(); |
| } else if (state == ActivityStatus.RESUMED) { |
| assert !mIsRunning; |
| if (mShouldRunAfterActivityResume) { |
| registerForLocationUpdates(); |
| } |
| } |
| } |
| |
| /** |
| * Start listening for location updates. |
| * @param gpsEnabled Whether or not we're interested in high accuracy GPS. |
| */ |
| private void start(boolean gpsEnabled) { |
| if (!mIsRunning && !mShouldRunAfterActivityResume) { |
| // Currently idle so start listening to activity status changes. |
| ActivityStatus.registerStateListener(this); |
| } |
| mIsGpsEnabled = gpsEnabled; |
| |
| if (ActivityStatus.getState() != ActivityStatus.RESUMED) { |
| mShouldRunAfterActivityResume = true; |
| } else { |
| unregisterFromLocationUpdates(); |
| registerForLocationUpdates(); |
| } |
| } |
| |
| /** |
| * Stop listening for location updates. |
| */ |
| private void stop() { |
| unregisterFromLocationUpdates(); |
| ActivityStatus.unregisterStateListener(this); |
| mShouldRunAfterActivityResume = false; |
| } |
| |
| /** |
| * Returns true if we are currently listening for location updates, false if not. |
| */ |
| private boolean isRunning() { |
| return mIsRunning; |
| } |
| |
| @Override |
| public void onLocationChanged(Location location) { |
| // Callbacks from the system location sevice are queued to this thread, so it's |
| // possible that we receive callbacks after unregistering. At this point, the |
| // native object will no longer exist. |
| if (mIsRunning) { |
| updateNewLocation(location); |
| } |
| } |
| |
| private void updateNewLocation(Location location) { |
| nativeNewLocationAvailable(location.getLatitude(), location.getLongitude(), |
| location.getTime() / 1000.0, |
| location.hasAltitude(), location.getAltitude(), |
| location.hasAccuracy(), location.getAccuracy(), |
| location.hasBearing(), location.getBearing(), |
| location.hasSpeed(), location.getSpeed()); |
| } |
| |
| @Override |
| public void onStatusChanged(String provider, int status, Bundle extras) { |
| } |
| |
| @Override |
| public void onProviderEnabled(String provider) { |
| } |
| |
| @Override |
| public void onProviderDisabled(String provider) { |
| } |
| |
| private void ensureLocationManagerCreated() { |
| if (mLocationManager != null) return; |
| mLocationManager = (LocationManager) mContext.getSystemService( |
| Context.LOCATION_SERVICE); |
| if (mLocationManager == null) { |
| Log.e(TAG, "Could not get location manager."); |
| } |
| } |
| |
| /** |
| * Registers this object with the location service. |
| */ |
| private void registerForLocationUpdates() { |
| ensureLocationManagerCreated(); |
| if (usePassiveOneShotLocation()) return; |
| |
| assert !mIsRunning; |
| mIsRunning = true; |
| |
| // We're running on the main thread. The C++ side is responsible to |
| // bounce notifications to the Geolocation thread as they arrive in the mainLooper. |
| try { |
| Criteria criteria = new Criteria(); |
| mLocationManager.requestLocationUpdates(0, 0, criteria, this, |
| ThreadUtils.getUiThreadLooper()); |
| if (mIsGpsEnabled) { |
| criteria.setAccuracy(Criteria.ACCURACY_FINE); |
| mLocationManager.requestLocationUpdates(0, 0, criteria, this, |
| ThreadUtils.getUiThreadLooper()); |
| } |
| } catch (SecurityException e) { |
| Log.e(TAG, "Caught security exception registering for location updates from " + |
| "system. This should only happen in DumpRenderTree."); |
| } catch (IllegalArgumentException e) { |
| Log.e(TAG, "Caught IllegalArgumentException registering for location updates."); |
| } |
| } |
| |
| /** |
| * Unregisters this object from the location service. |
| */ |
| private void unregisterFromLocationUpdates() { |
| if (mIsRunning) { |
| mIsRunning = false; |
| mLocationManager.removeUpdates(this); |
| } |
| } |
| |
| private boolean usePassiveOneShotLocation() { |
| if (!isOnlyPassiveLocationProviderEnabled()) return false; |
| |
| // Do not request a location update if the only available location provider is |
| // the passive one. Make use of the last known location and call |
| // onLocationChanged directly. |
| final Location location = mLocationManager.getLastKnownLocation( |
| LocationManager.PASSIVE_PROVIDER); |
| if (location != null) { |
| ThreadUtils.runOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| updateNewLocation(location); |
| } |
| }); |
| } |
| return true; |
| } |
| |
| /* |
| * Checks if the passive location provider is the only provider available |
| * in the system. |
| */ |
| private boolean isOnlyPassiveLocationProviderEnabled() { |
| List<String> providers = mLocationManager.getProviders(true); |
| return providers != null && providers.size() == 1 |
| && providers.get(0).equals(LocationManager.PASSIVE_PROVIDER); |
| } |
| } |
| |
| // Delegate handling the real work in the main thread. |
| private LocationProviderImpl mImpl; |
| |
| private LocationProvider(Context context) { |
| mImpl = new LocationProviderImpl(context); |
| } |
| |
| @CalledByNative |
| static LocationProvider create(Context context) { |
| return new LocationProvider(context); |
| } |
| |
| /** |
| * Start listening for location updates until we're told to quit. May be |
| * called in any thread. |
| * @param gpsEnabled Whether or not we're interested in high accuracy GPS. |
| */ |
| @CalledByNative |
| public boolean start(final boolean gpsEnabled) { |
| FutureTask<Void> task = new FutureTask<Void>(new Runnable() { |
| @Override |
| public void run() { |
| mImpl.start(gpsEnabled); |
| } |
| }, null); |
| ThreadUtils.runOnUiThread(task); |
| return true; |
| } |
| |
| /** |
| * Stop listening for location updates. May be called in any thread. |
| */ |
| @CalledByNative |
| public void stop() { |
| FutureTask<Void> task = new FutureTask<Void>(new Runnable() { |
| @Override |
| public void run() { |
| mImpl.stop(); |
| } |
| }, null); |
| ThreadUtils.runOnUiThread(task); |
| } |
| |
| /** |
| * Returns true if we are currently listening for location updates, false if not. |
| * Must be called only in the UI thread. |
| */ |
| public boolean isRunning() { |
| assert ThreadUtils.runningOnUiThread(); |
| return mImpl.isRunning(); |
| } |
| |
| // Native functions |
| public static native void nativeNewLocationAvailable( |
| double latitude, double longitude, double timeStamp, |
| boolean hasAltitude, double altitude, |
| boolean hasAccuracy, double accuracy, |
| boolean hasHeading, double heading, |
| boolean hasSpeed, double speed); |
| public static native void nativeNewErrorAvailable(String message); |
| } |