// Copyright (c) 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.os.Looper; 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; int activityState = ActivityStatus.getState(); if (activityState == ActivityStatus.PAUSED || activityState == ActivityStatus.STOPPED || activityState == ActivityStatus.DESTROYED) { 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, Looper.getMainLooper()); if (mIsGpsEnabled) { criteria.setAccuracy(Criteria.ACCURACY_FINE); mLocationManager.requestLocationUpdates(0, 0, criteria, this, Looper.getMainLooper()); } } 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 Looper.myLooper() == Looper.getMainLooper(); 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); }