// Copyright 2015 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.chrome.browser.omnibox.geo; import android.content.Context; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.os.Handler; import org.chromium.base.ThreadUtils; import org.chromium.base.VisibleForTesting; import org.chromium.base.annotations.SuppressFBWarnings; /** * Keeps track of the device's location, allowing synchronous location requests. * getLastKnownLocation() returns the current best estimate of the location. If possible, call * refreshLastKnownLocation() several seconds before a location is needed to maximize the chances * that the location is known. */ class GeolocationTracker { private static SelfCancelingListener sListener; private static Location sLocationForTesting; private static boolean sUseLocationForTesting; private static class SelfCancelingListener implements LocationListener { // Length of time before the location request should be canceled. This timeout ensures the // device doesn't get stuck in an infinite loop trying and failing to get a location, which // would cause battery drain. See: http://crbug.com/309917 private static final int REQUEST_TIMEOUT_MS = 60 * 1000; // 60 sec. private final LocationManager mLocationManager; private final Handler mHandler; private final Runnable mCancelRunnable; private SelfCancelingListener(LocationManager manager) { mLocationManager = manager; mHandler = new Handler(); mCancelRunnable = new Runnable() { @Override public void run() { mLocationManager.removeUpdates(SelfCancelingListener.this); sListener = null; } }; mHandler.postDelayed(mCancelRunnable, REQUEST_TIMEOUT_MS); } @Override public void onLocationChanged(Location location) { mHandler.removeCallbacks(mCancelRunnable); sListener = null; } @Override public void onProviderDisabled(String provider) { } @Override public void onProviderEnabled(String provider) { } @Override public void onStatusChanged(String provider, int status, Bundle extras) { } } /** * Returns the age of location is milliseconds. * Note: the age will be invalid if the system clock has been changed since the location was * created. If the apparent age is negative, Long.MAX_VALUE will be returned. */ static long getLocationAge(Location location) { long age = System.currentTimeMillis() - location.getTime(); return age >= 0 ? age : Long.MAX_VALUE; } /** * Returns the last known location from the network provider or null if none is available. */ static Location getLastKnownLocation(Context context) { if (sUseLocationForTesting) return sLocationForTesting; LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); return locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); } /** * Requests an updated location if the last known location is older than maxAge milliseconds. * * Note: this must be called only on the UI thread. */ @SuppressFBWarnings("LI_LAZY_INIT_UPDATE_STATIC") static void refreshLastKnownLocation(Context context, long maxAge) { ThreadUtils.assertOnUiThread(); // We're still waiting for a location update. if (sListener != null) return; LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); Location location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); if (location == null || getLocationAge(location) > maxAge) { String provider = LocationManager.NETWORK_PROVIDER; if (locationManager.isProviderEnabled(provider)) { sListener = new SelfCancelingListener(locationManager); locationManager.requestSingleUpdate(provider, sListener, null); } } } @VisibleForTesting static void setLocationForTesting(Location location) { sLocationForTesting = location; sUseLocationForTesting = true; } }