summaryrefslogtreecommitdiffstats
path: root/services/java/com/android/server/LocationManagerService.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/java/com/android/server/LocationManagerService.java')
-rw-r--r--services/java/com/android/server/LocationManagerService.java2753
1 files changed, 2753 insertions, 0 deletions
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
new file mode 100644
index 0000000..9d69114
--- /dev/null
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -0,0 +1,2753 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.location.Address;
+import android.location.IGpsStatusListener;
+import android.location.ILocationListener;
+import android.location.ILocationManager;
+import android.location.Location;
+import android.location.LocationManager;
+import android.location.LocationProvider;
+import android.location.LocationProviderImpl;
+import android.net.ConnectivityManager;
+import android.net.Uri;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.telephony.CellLocation;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
+import android.util.Config;
+import android.util.Log;
+import android.util.PrintWriterPrinter;
+import android.util.SparseIntArray;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.location.CellState;
+import com.android.internal.location.GpsLocationProvider;
+import com.android.internal.location.ILocationCollector;
+import com.android.internal.location.INetworkLocationManager;
+import com.android.internal.location.INetworkLocationProvider;
+import com.android.internal.location.TrackProvider;
+import com.android.server.am.BatteryStatsService;
+
+/**
+ * The service class that manages LocationProviders and issues location
+ * updates and alerts.
+ *
+ * {@hide}
+ */
+public class LocationManagerService extends ILocationManager.Stub
+ implements INetworkLocationManager {
+ private static final String TAG = "LocationManagerService";
+
+ // Minimum time interval between last known location writes, in milliseconds.
+ private static final long MIN_LAST_KNOWN_LOCATION_TIME = 60L * 1000L;
+
+ // Max time to hold wake lock for, in milliseconds.
+ private static final long MAX_TIME_FOR_WAKE_LOCK = 60 * 1000L;
+
+ // Time to wait after releasing a wake lock for clients to process location update,
+ // in milliseconds.
+ private static final long TIME_AFTER_WAKE_LOCK = 2 * 1000L;
+
+ // The last time a location was written, by provider name.
+ private HashMap<String,Long> mLastWriteTime = new HashMap<String,Long>();
+
+ private static final Pattern PATTERN_COMMA = Pattern.compile(",");
+
+ private static final String ACCESS_FINE_LOCATION =
+ android.Manifest.permission.ACCESS_FINE_LOCATION;
+ private static final String ACCESS_COARSE_LOCATION =
+ android.Manifest.permission.ACCESS_COARSE_LOCATION;
+ private static final String ACCESS_MOCK_LOCATION =
+ android.Manifest.permission.ACCESS_MOCK_LOCATION;
+ private static final String ACCESS_LOCATION_EXTRA_COMMANDS =
+ android.Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS;
+
+ // Set of providers that are explicitly enabled
+ private final Set<String> mEnabledProviders = new HashSet<String>();
+
+ // Set of providers that are explicitly disabled
+ private final Set<String> mDisabledProviders = new HashSet<String>();
+
+ // Locations, status values, and extras for mock providers
+ HashMap<String,MockProvider> mMockProviders = new HashMap<String,MockProvider>();
+ private final HashMap<String,Location> mMockProviderLocation = new HashMap<String,Location>();
+ private final HashMap<String,Integer> mMockProviderStatus = new HashMap<String,Integer>();
+ private final HashMap<String,Bundle> mMockProviderStatusExtras = new HashMap<String,Bundle>();
+ private final HashMap<String,Long> mMockProviderStatusUpdateTime = new HashMap<String,Long>();
+
+ private static boolean sProvidersLoaded = false;
+
+ private final Context mContext;
+ private GpsLocationProvider mGpsLocationProvider;
+ private boolean mGpsNavigating;
+ private LocationProviderImpl mNetworkLocationProvider;
+ private INetworkLocationProvider mNetworkLocationInterface;
+ private LocationWorkerHandler mLocationHandler;
+
+ // Handler messages
+ private static final int MESSAGE_HEARTBEAT = 1;
+ private static final int MESSAGE_ACQUIRE_WAKE_LOCK = 2;
+ private static final int MESSAGE_RELEASE_WAKE_LOCK = 3;
+ private static final int MESSAGE_INSTALL_NETWORK_LOCATION_PROVIDER = 4;
+
+ // Alarm manager and wakelock variables
+ private final static String ALARM_INTENT = "com.android.location.ALARM_INTENT";
+ private final static String WAKELOCK_KEY = "LocationManagerService";
+ private final static String WIFILOCK_KEY = "LocationManagerService";
+ private AlarmManager mAlarmManager;
+ private long mAlarmInterval = 0;
+ private boolean mScreenOn = true;
+ private PowerManager.WakeLock mWakeLock = null;
+ private WifiManager.WifiLock mWifiLock = null;
+ private long mWakeLockAcquireTime = 0;
+ private boolean mWakeLockGpsReceived = true;
+ private boolean mWakeLockNetworkReceived = true;
+ private boolean mWifiWakeLockAcquired = false;
+ private boolean mCellWakeLockAcquired = false;
+
+ private final IBatteryStats mBatteryStats;
+
+ /**
+ * Mapping from listener IBinder/PendingIntent to local Listener wrappers.
+ */
+ private final ArrayList<Receiver> mListeners = new ArrayList<Receiver>();
+
+ /**
+ * Used for reporting which UIDs are causing the GPS to run.
+ */
+ private final SparseIntArray mReportedGpsUids = new SparseIntArray();
+ private int mReportedGpsSeq = 0;
+
+ /**
+ * Mapping from listener IBinder/PendingIntent to a map from provider name to UpdateRecord.
+ * This also serves as the lock for our state.
+ */
+ private final HashMap<Receiver,HashMap<String,UpdateRecord>> mLocationListeners =
+ new HashMap<Receiver,HashMap<String,UpdateRecord>>();
+
+ /**
+ * Mapping from listener IBinder/PendingIntent to a map from provider name to last broadcast
+ * location.
+ */
+ private final HashMap<Receiver,HashMap<String,Location>> mLastFixBroadcast =
+ new HashMap<Receiver,HashMap<String,Location>>();
+ private final HashMap<Receiver,HashMap<String,Long>> mLastStatusBroadcast =
+ new HashMap<Receiver,HashMap<String,Long>>();
+
+ /**
+ * Mapping from provider name to all its UpdateRecords
+ */
+ private final HashMap<String,ArrayList<UpdateRecord>> mRecordsByProvider =
+ new HashMap<String,ArrayList<UpdateRecord>>();
+
+ /**
+ * Mappings from provider name to object to use for current location. Locations
+ * contained in this list may not always be valid.
+ */
+ private final HashMap<String,Location> mLocationsByProvider =
+ new HashMap<String,Location>();
+
+ // Proximity listeners
+ private Receiver mProximityListener = null;
+ private HashMap<PendingIntent,ProximityAlert> mProximityAlerts =
+ new HashMap<PendingIntent,ProximityAlert>();
+ private HashSet<ProximityAlert> mProximitiesEntered =
+ new HashSet<ProximityAlert>();
+
+ // Last known location for each provider
+ private HashMap<String,Location> mLastKnownLocation =
+ new HashMap<String,Location>();
+
+ // Battery status extras (from com.android.server.BatteryService)
+ private static final String BATTERY_EXTRA_SCALE = "scale";
+ private static final String BATTERY_EXTRA_LEVEL = "level";
+ private static final String BATTERY_EXTRA_PLUGGED = "plugged";
+
+ // Last known cell service state
+ private TelephonyManager mTelephonyManager;
+
+ // Location collector
+ private ILocationCollector mCollector;
+
+ // Wifi Manager
+ private WifiManager mWifiManager;
+
+ /**
+ * A wrapper class holding either an ILocationListener or a PendingIntent to receive
+ * location updates.
+ */
+ private final class Receiver implements IBinder.DeathRecipient {
+ final ILocationListener mListener;
+ final PendingIntent mPendingIntent;
+ final int mUid;
+ final Object mKey;
+
+ Receiver(ILocationListener listener, int uid) {
+ mListener = listener;
+ mPendingIntent = null;
+ mUid = uid;
+ mKey = listener.asBinder();
+ }
+
+ Receiver(PendingIntent intent, int uid) {
+ mPendingIntent = intent;
+ mListener = null;
+ mUid = uid;
+ mKey = intent;
+ }
+
+ @Override
+ public boolean equals(Object otherObj) {
+ if (otherObj instanceof Receiver) {
+ return mKey.equals(
+ ((Receiver)otherObj).mKey);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mKey.hashCode();
+ }
+
+
+ @Override
+ public String toString() {
+ if (mListener != null) {
+ return "Receiver{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " uid " + mUid + " Listener " + mKey + "}";
+ } else {
+ return "Receiver{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " uid " + mUid + " Intent " + mKey + "}";
+ }
+ }
+
+ public boolean isListener() {
+ return mListener != null;
+ }
+
+ public boolean isPendingIntent() {
+ return mPendingIntent != null;
+ }
+
+ public ILocationListener getListener() {
+ if (mListener != null) {
+ return mListener;
+ }
+ throw new IllegalStateException("Request for non-existent listener");
+ }
+
+ public PendingIntent getPendingIntent() {
+ if (mPendingIntent != null) {
+ return mPendingIntent;
+ }
+ throw new IllegalStateException("Request for non-existent intent");
+ }
+
+ public boolean callStatusChangedLocked(String provider, int status, Bundle extras) {
+ if (mListener != null) {
+ try {
+ mListener.onStatusChanged(provider, status, extras);
+ } catch (RemoteException e) {
+ return false;
+ }
+ } else {
+ Intent statusChanged = new Intent();
+ statusChanged.putExtras(extras);
+ statusChanged.putExtra(LocationManager.KEY_STATUS_CHANGED, status);
+ try {
+ mPendingIntent.send(mContext, 0, statusChanged, null, null);
+ } catch (PendingIntent.CanceledException e) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean callLocationChangedLocked(Location location) {
+ if (mListener != null) {
+ try {
+ mListener.onLocationChanged(location);
+ } catch (RemoteException e) {
+ return false;
+ }
+ } else {
+ Intent locationChanged = new Intent();
+ locationChanged.putExtra(LocationManager.KEY_LOCATION_CHANGED, location);
+ try {
+ mPendingIntent.send(mContext, 0, locationChanged, null, null);
+ } catch (PendingIntent.CanceledException e) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void binderDied() {
+ if (Config.LOGD) {
+ Log.d(TAG, "Location listener died");
+ }
+ synchronized (mLocationListeners) {
+ removeUpdatesLocked(this);
+ }
+ }
+ }
+
+ private Location readLastKnownLocationLocked(String provider) {
+ Location location = null;
+ String s = null;
+ try {
+ File f = new File(LocationManager.SYSTEM_DIR + "/location."
+ + provider);
+ if (!f.exists()) {
+ return null;
+ }
+ BufferedReader reader = new BufferedReader(new FileReader(f), 256);
+ s = reader.readLine();
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to read last known location", e);
+ }
+
+ if (s == null) {
+ return null;
+ }
+ try {
+ String[] tokens = PATTERN_COMMA.split(s);
+ int idx = 0;
+ long time = Long.parseLong(tokens[idx++]);
+ double latitude = Double.parseDouble(tokens[idx++]);
+ double longitude = Double.parseDouble(tokens[idx++]);
+ double altitude = Double.parseDouble(tokens[idx++]);
+ float bearing = Float.parseFloat(tokens[idx++]);
+ float speed = Float.parseFloat(tokens[idx++]);
+
+ location = new Location(provider);
+ location.setTime(time);
+ location.setLatitude(latitude);
+ location.setLongitude(longitude);
+ location.setAltitude(altitude);
+ location.setBearing(bearing);
+ location.setSpeed(speed);
+ } catch (NumberFormatException nfe) {
+ Log.e(TAG, "NumberFormatException reading last known location", nfe);
+ return null;
+ }
+
+ return location;
+ }
+
+ private void writeLastKnownLocationLocked(String provider,
+ Location location) {
+ long now = SystemClock.elapsedRealtime();
+ Long last = mLastWriteTime.get(provider);
+ if ((last != null)
+ && (now - last.longValue() < MIN_LAST_KNOWN_LOCATION_TIME)) {
+ return;
+ }
+ mLastWriteTime.put(provider, now);
+
+ StringBuilder sb = new StringBuilder(100);
+ sb.append(location.getTime());
+ sb.append(',');
+ sb.append(location.getLatitude());
+ sb.append(',');
+ sb.append(location.getLongitude());
+ sb.append(',');
+ sb.append(location.getAltitude());
+ sb.append(',');
+ sb.append(location.getBearing());
+ sb.append(',');
+ sb.append(location.getSpeed());
+
+ FileWriter writer = null;
+ try {
+ File d = new File(LocationManager.SYSTEM_DIR);
+ if (!d.exists()) {
+ if (!d.mkdirs()) {
+ Log.w(TAG, "Unable to create directory to write location");
+ return;
+ }
+ }
+ File f = new File(LocationManager.SYSTEM_DIR + "/location." + provider);
+ writer = new FileWriter(f);
+ writer.write(sb.toString());
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to write location", e);
+ } finally {
+ if (writer != null) {
+ try {
+ writer.close();
+ } catch (IOException e) {
+ Log.w(TAG, "Exception closing file", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Load providers from /data/location/<provider_name>/
+ * class
+ * kml
+ * nmea
+ * track
+ * location
+ * properties
+ */
+ private void loadProviders() {
+ synchronized (mLocationListeners) {
+ if (sProvidersLoaded) {
+ return;
+ }
+
+ // Load providers
+ loadProvidersLocked();
+ sProvidersLoaded = true;
+ }
+ }
+
+ private void loadProvidersLocked() {
+ try {
+ _loadProvidersLocked();
+ } catch (Exception e) {
+ Log.e(TAG, "Exception loading providers:", e);
+ }
+ }
+
+ private void _loadProvidersLocked() {
+ // Attempt to load "real" providers first
+ if (GpsLocationProvider.isSupported()) {
+ // Create a gps location provider
+ mGpsLocationProvider = new GpsLocationProvider(mContext);
+ LocationProviderImpl.addProvider(mGpsLocationProvider);
+ }
+
+ // Load fake providers if real providers are not available
+ File f = new File(LocationManager.PROVIDER_DIR);
+ if (f.isDirectory()) {
+ File[] subdirs = f.listFiles();
+ for (int i = 0; i < subdirs.length; i++) {
+ if (!subdirs[i].isDirectory()) {
+ continue;
+ }
+
+ String name = subdirs[i].getName();
+
+ if (Config.LOGD) {
+ Log.d(TAG, "Found dir " + subdirs[i].getAbsolutePath());
+ Log.d(TAG, "name = " + name);
+ }
+
+ // Don't create a fake provider if a real provider exists
+ if (LocationProviderImpl.getProvider(name) == null) {
+ LocationProviderImpl provider = null;
+ try {
+ File classFile = new File(subdirs[i], "class");
+ // Look for a 'class' file
+ provider = LocationProviderImpl.loadFromClass(classFile);
+
+ // Look for an 'kml', 'nmea', or 'track' file
+ if (provider == null) {
+ // Load properties from 'properties' file, if present
+ File propertiesFile = new File(subdirs[i], "properties");
+
+ if (propertiesFile.exists()) {
+ provider = new TrackProvider(name);
+ ((TrackProvider)provider).readProperties(propertiesFile);
+
+ File kmlFile = new File(subdirs[i], "kml");
+ if (kmlFile.exists()) {
+ ((TrackProvider) provider).readKml(kmlFile);
+ } else {
+ File nmeaFile = new File(subdirs[i], "nmea");
+ if (nmeaFile.exists()) {
+ ((TrackProvider) provider).readNmea(name, nmeaFile);
+ } else {
+ File trackFile = new File(subdirs[i], "track");
+ if (trackFile.exists()) {
+ ((TrackProvider) provider).readTrack(trackFile);
+ }
+ }
+ }
+ }
+ }
+ if (provider != null) {
+ LocationProviderImpl.addProvider(provider);
+ }
+ // Grab the initial location of a TrackProvider and
+ // store it as the last known location for that provider
+ if (provider instanceof TrackProvider) {
+ TrackProvider tp = (TrackProvider) provider;
+ mLastKnownLocation.put(tp.getName(), tp.getInitialLocation());
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception loading provder " + name, e);
+ }
+ }
+ }
+ }
+
+ updateProvidersLocked();
+ }
+
+ /**
+ * @param context the context that the LocationManagerService runs in
+ */
+ public LocationManagerService(Context context) {
+ super();
+ mContext = context;
+ mLocationHandler = new LocationWorkerHandler();
+
+ if (Config.LOGD) {
+ Log.d(TAG, "Constructed LocationManager Service");
+ }
+
+ // Alarm manager, needs to be done before calling loadProviders() below
+ mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+
+ // Create a wake lock, needs to be done before calling loadProviders() below
+ PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
+
+ // Battery statistics service to be notified when GPS turns on or off
+ mBatteryStats = BatteryStatsService.getService();
+
+ // Load providers
+ loadProviders();
+
+ // Listen for Radio changes
+ mTelephonyManager = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
+ mTelephonyManager.listen(mPhoneStateListener,
+ PhoneStateListener.LISTEN_CELL_LOCATION |
+ PhoneStateListener.LISTEN_SIGNAL_STRENGTH |
+ PhoneStateListener.LISTEN_DATA_CONNECTION_STATE);
+
+ // Register for Network (Wifi or Mobile) updates
+ NetworkStateBroadcastReceiver networkReceiver = new NetworkStateBroadcastReceiver();
+ IntentFilter networkIntentFilter = new IntentFilter();
+ networkIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+ networkIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ networkIntentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ networkIntentFilter.addAction(GpsLocationProvider.GPS_ENABLED_CHANGE_ACTION);
+ context.registerReceiver(networkReceiver, networkIntentFilter);
+
+ // Register for power updates
+ PowerStateBroadcastReceiver powerStateReceiver = new PowerStateBroadcastReceiver();
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(ALARM_INTENT);
+ intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+ intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+ intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+ context.registerReceiver(powerStateReceiver, intentFilter);
+
+ // Get the wifi manager
+ mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+
+ // Create a wifi lock for future use
+ mWifiLock = getWifiWakelockLocked();
+
+ // There might be an existing wifi scan available
+ if (mWifiManager != null) {
+ List<ScanResult> wifiScanResults = mWifiManager.getScanResults();
+ if (wifiScanResults != null && wifiScanResults.size() != 0) {
+ if (mNetworkLocationInterface != null) {
+ mNetworkLocationInterface.updateWifiScanResults(wifiScanResults);
+ }
+ }
+ }
+ }
+
+ public void setInstallCallback(InstallCallback callback) {
+ synchronized (mLocationListeners) {
+ mLocationHandler.removeMessages(MESSAGE_INSTALL_NETWORK_LOCATION_PROVIDER);
+ Message m = Message.obtain(mLocationHandler,
+ MESSAGE_INSTALL_NETWORK_LOCATION_PROVIDER, callback);
+ mLocationHandler.sendMessageAtFrontOfQueue(m);
+ }
+ }
+
+ public void setNetworkLocationProvider(INetworkLocationProvider provider) {
+ synchronized (mLocationListeners) {
+ mNetworkLocationInterface = provider;
+ provider.addListener(getPackageNames());
+ mNetworkLocationProvider = (LocationProviderImpl)provider;
+ LocationProviderImpl.addProvider(mNetworkLocationProvider);
+ updateProvidersLocked();
+ }
+ }
+
+ public void setLocationCollector(ILocationCollector collector) {
+ synchronized (mLocationListeners) {
+ mCollector = collector;
+ if (mGpsLocationProvider != null) {
+ mGpsLocationProvider.setLocationCollector(mCollector);
+ }
+ }
+ }
+
+ private WifiManager.WifiLock getWifiWakelockLocked() {
+ if (mWifiLock == null && mWifiManager != null) {
+ mWifiLock = mWifiManager.createWifiLock(WifiManager.WIFI_MODE_SCAN_ONLY, WIFILOCK_KEY);
+ mWifiLock.setReferenceCounted(false);
+ }
+ return mWifiLock;
+ }
+
+ private boolean isAllowedBySettingsLocked(String provider) {
+ if (mEnabledProviders.contains(provider)) {
+ return true;
+ }
+ if (mDisabledProviders.contains(provider)) {
+ return false;
+ }
+ // Use system settings
+ ContentResolver resolver = mContext.getContentResolver();
+ String allowedProviders = Settings.Secure.getString(resolver,
+ Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
+
+ return ((allowedProviders != null) && (allowedProviders.contains(provider)));
+ }
+
+ private void checkPermissionsSafe(String provider) {
+ if (LocationManager.GPS_PROVIDER.equals(provider)
+ && (mContext.checkCallingPermission(ACCESS_FINE_LOCATION)
+ != PackageManager.PERMISSION_GRANTED)) {
+ throw new SecurityException("Requires ACCESS_FINE_LOCATION permission");
+ }
+ if (LocationManager.NETWORK_PROVIDER.equals(provider)
+ && (mContext.checkCallingPermission(ACCESS_FINE_LOCATION)
+ != PackageManager.PERMISSION_GRANTED)
+ && (mContext.checkCallingPermission(ACCESS_COARSE_LOCATION)
+ != PackageManager.PERMISSION_GRANTED)) {
+ throw new SecurityException(
+ "Requires ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION permission");
+ }
+ }
+
+ private boolean isAllowedProviderSafe(String provider) {
+ if (LocationManager.GPS_PROVIDER.equals(provider)
+ && (mContext.checkCallingPermission(ACCESS_FINE_LOCATION)
+ != PackageManager.PERMISSION_GRANTED)) {
+ return false;
+ }
+ if (LocationManager.NETWORK_PROVIDER.equals(provider)
+ && (mContext.checkCallingPermission(ACCESS_FINE_LOCATION)
+ != PackageManager.PERMISSION_GRANTED)
+ && (mContext.checkCallingPermission(ACCESS_COARSE_LOCATION)
+ != PackageManager.PERMISSION_GRANTED)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private String[] getPackageNames() {
+ // Since a single UID may correspond to multiple packages, this can only be used as an
+ // approximation for tracking
+ return mContext.getPackageManager().getPackagesForUid(Binder.getCallingUid());
+ }
+
+ public List<String> getAllProviders() {
+ try {
+ synchronized (mLocationListeners) {
+ return _getAllProvidersLocked();
+ }
+ } catch (SecurityException se) {
+ throw se;
+ } catch (Exception e) {
+ Log.e(TAG, "getAllProviders got exception:", e);
+ return null;
+ }
+ }
+
+ private List<String> _getAllProvidersLocked() {
+ if (Config.LOGD) {
+ Log.d(TAG, "getAllProviders");
+ }
+ List<LocationProviderImpl> providers = LocationProviderImpl.getProviders();
+ ArrayList<String> out = new ArrayList<String>(providers.size());
+
+ for (LocationProviderImpl p : providers) {
+ out.add(p.getName());
+ }
+ return out;
+ }
+
+ public List<String> getProviders(boolean enabledOnly) {
+ try {
+ synchronized (mLocationListeners) {
+ return _getProvidersLocked(enabledOnly);
+ }
+ } catch (SecurityException se) {
+ throw se;
+ } catch (Exception e) {
+ Log.e(TAG, "getProviders gotString exception:", e);
+ return null;
+ }
+ }
+
+ private List<String> _getProvidersLocked(boolean enabledOnly) {
+ if (Config.LOGD) {
+ Log.d(TAG, "getProviders");
+ }
+ List<LocationProviderImpl> providers = LocationProviderImpl.getProviders();
+ ArrayList<String> out = new ArrayList<String>();
+
+ for (LocationProviderImpl p : providers) {
+ String name = p.getName();
+ if (isAllowedProviderSafe(name)) {
+ if (enabledOnly && !isAllowedBySettingsLocked(name)) {
+ continue;
+ }
+ out.add(name);
+ }
+ }
+ return out;
+ }
+
+ public void updateProviders() {
+ synchronized (mLocationListeners) {
+ updateProvidersLocked();
+ }
+ }
+
+ private void updateProvidersLocked() {
+ for (LocationProviderImpl p : LocationProviderImpl.getProviders()) {
+ boolean isEnabled = p.isEnabled();
+ String name = p.getName();
+ boolean shouldBeEnabled = isAllowedBySettingsLocked(name);
+
+ // Collection is only allowed when network provider is being used
+ if (mCollector != null &&
+ p.getName().equals(LocationManager.NETWORK_PROVIDER)) {
+ mCollector.updateNetworkProviderStatus(shouldBeEnabled);
+ }
+
+ if (isEnabled && !shouldBeEnabled) {
+ updateProviderListenersLocked(name, false);
+ } else if (!isEnabled && shouldBeEnabled) {
+ updateProviderListenersLocked(name, true);
+ }
+
+ }
+ }
+
+ private void updateProviderListenersLocked(String provider, boolean enabled) {
+ int listeners = 0;
+
+ LocationProviderImpl p = LocationProviderImpl.getProvider(provider);
+ if (p == null) {
+ return;
+ }
+
+ ArrayList<Receiver> deadReceivers = null;
+
+ ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
+ if (records != null) {
+ final int N = records.size();
+ for (int i=0; i<N; i++) {
+ UpdateRecord record = records.get(i);
+ // Sends a notification message to the receiver
+ try {
+ Receiver receiver = record.mReceiver;
+ if (receiver.isListener()) {
+ if (enabled) {
+ receiver.getListener().onProviderEnabled(provider);
+ } else {
+ receiver.getListener().onProviderDisabled(provider);
+ }
+ } else {
+ Intent providerIntent = new Intent();
+ providerIntent.putExtra(LocationManager.KEY_PROVIDER_ENABLED, enabled);
+ try {
+ receiver.getPendingIntent().send(mContext, 0,
+ providerIntent, null, null);
+ } catch (PendingIntent.CanceledException e) {
+ if (deadReceivers == null) {
+ deadReceivers = new ArrayList<Receiver>();
+ deadReceivers.add(receiver);
+ }
+ }
+ }
+ } catch (RemoteException e) {
+ // The death link will clean this up.
+ }
+ listeners++;
+ }
+ }
+
+ if (deadReceivers != null) {
+ for (int i=deadReceivers.size()-1; i>=0; i--) {
+ removeUpdatesLocked(deadReceivers.get(i));
+ }
+ }
+
+ if (enabled) {
+ p.enable();
+ if (listeners > 0) {
+ p.setMinTime(getMinTimeLocked(provider));
+ p.enableLocationTracking(true);
+ updateWakelockStatusLocked(mScreenOn);
+ }
+ } else {
+ p.enableLocationTracking(false);
+ if (p == mGpsLocationProvider) {
+ mGpsNavigating = false;
+ reportStopGpsLocked();
+ }
+ p.disable();
+ updateWakelockStatusLocked(mScreenOn);
+ }
+
+ if (enabled && listeners > 0) {
+ mLocationHandler.removeMessages(MESSAGE_HEARTBEAT, provider);
+ Message m = Message.obtain(mLocationHandler, MESSAGE_HEARTBEAT, provider);
+ mLocationHandler.sendMessageAtTime(m, SystemClock.uptimeMillis() + 1000);
+ } else {
+ mLocationHandler.removeMessages(MESSAGE_HEARTBEAT, provider);
+ }
+ }
+
+ private long getMinTimeLocked(String provider) {
+ long minTime = Long.MAX_VALUE;
+ ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
+ if (records != null) {
+ for (int i=records.size()-1; i>=0; i--) {
+ minTime = Math.min(minTime, records.get(i).mMinTime);
+ }
+ }
+ return minTime;
+ }
+
+ private class UpdateRecord {
+ final String mProvider;
+ final Receiver mReceiver;
+ final long mMinTime;
+ final float mMinDistance;
+ final int mUid;
+ final String[] mPackages;
+
+ /**
+ * Note: must be constructed with lock held.
+ */
+ UpdateRecord(String provider, long minTime, float minDistance,
+ Receiver receiver, int uid, String[] packages) {
+ mProvider = provider;
+ mReceiver = receiver;
+ mMinTime = minTime;
+ mMinDistance = minDistance;
+ mUid = uid;
+ mPackages = packages;
+
+ ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
+ if (records == null) {
+ records = new ArrayList<UpdateRecord>();
+ mRecordsByProvider.put(provider, records);
+ }
+ if (!records.contains(this)) {
+ records.add(this);
+ }
+ }
+
+ /**
+ * Method to be called when a record will no longer be used. Calling this multiple times
+ * must have the same effect as calling it once.
+ */
+ void disposeLocked() {
+ ArrayList<UpdateRecord> records = mRecordsByProvider.get(this.mProvider);
+ records.remove(this);
+ }
+
+ @Override
+ public String toString() {
+ return "UpdateRecord{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + mProvider + " " + mReceiver + "}";
+ }
+
+ void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + this);
+ pw.println(prefix + "mProvider=" + mProvider + " mReceiver=" + mReceiver);
+ pw.println(prefix + "mMinTime=" + mMinTime + " mMinDistance=" + mMinDistance);
+ StringBuilder sb = new StringBuilder();
+ if (mPackages != null) {
+ for (int i=0; i<mPackages.length; i++) {
+ if (i > 0) sb.append(", ");
+ sb.append(mPackages[i]);
+ }
+ }
+ pw.println(prefix + "mUid=" + mUid + " mPackages=" + sb);
+ }
+
+ /**
+ * Calls dispose().
+ */
+ @Override protected void finalize() {
+ synchronized (mLocationListeners) {
+ disposeLocked();
+ }
+ }
+ }
+
+ public void requestLocationUpdates(String provider,
+ long minTime, float minDistance, ILocationListener listener) {
+
+ try {
+ synchronized (mLocationListeners) {
+ requestLocationUpdatesLocked(provider, minTime, minDistance,
+ new Receiver(listener, Binder.getCallingUid()));
+ }
+ } catch (SecurityException se) {
+ throw se;
+ } catch (Exception e) {
+ Log.e(TAG, "requestUpdates got exception:", e);
+ }
+ }
+
+ public void requestLocationUpdatesPI(String provider,
+ long minTime, float minDistance, PendingIntent intent) {
+ try {
+ synchronized (mLocationListeners) {
+ requestLocationUpdatesLocked(provider, minTime, minDistance,
+ new Receiver(intent, Binder.getCallingUid()));
+ }
+ } catch (SecurityException se) {
+ throw se;
+ } catch (Exception e) {
+ Log.e(TAG, "requestUpdates got exception:", e);
+ }
+ }
+
+ private void requestLocationUpdatesLocked(String provider,
+ long minTime, float minDistance, Receiver receiver) {
+ if (Config.LOGD) {
+ Log.d(TAG, "_requestLocationUpdates: listener = " + receiver);
+ }
+
+ LocationProviderImpl impl = LocationProviderImpl.getProvider(provider);
+ if (impl == null) {
+ throw new IllegalArgumentException("provider=" + provider);
+ }
+
+ checkPermissionsSafe(provider);
+
+ String[] packages = getPackageNames();
+
+ // so wakelock calls will succeed
+ final int callingUid = Binder.getCallingUid();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ UpdateRecord r = new UpdateRecord(provider, minTime, minDistance,
+ receiver, callingUid, packages);
+ if (!mListeners.contains(receiver)) {
+ try {
+ if (receiver.isListener()) {
+ receiver.getListener().asBinder().linkToDeath(receiver, 0);
+ }
+ mListeners.add(receiver);
+ } catch (RemoteException e) {
+ return;
+ }
+ }
+
+ HashMap<String,UpdateRecord> records = mLocationListeners.get(receiver);
+ if (records == null) {
+ records = new HashMap<String,UpdateRecord>();
+ mLocationListeners.put(receiver, records);
+ }
+ UpdateRecord oldRecord = records.put(provider, r);
+ if (oldRecord != null) {
+ oldRecord.disposeLocked();
+ }
+
+ boolean isProviderEnabled = isAllowedBySettingsLocked(provider);
+ if (isProviderEnabled) {
+ long minTimeForProvider = getMinTimeLocked(provider);
+ impl.setMinTime(minTimeForProvider);
+ impl.enableLocationTracking(true);
+ updateWakelockStatusLocked(mScreenOn);
+
+ if (provider.equals(LocationManager.GPS_PROVIDER)) {
+ if (mGpsNavigating) {
+ updateReportedGpsLocked();
+ }
+ }
+
+ // Clear heartbeats if any before starting a new one
+ mLocationHandler.removeMessages(MESSAGE_HEARTBEAT, provider);
+ Message m = Message.obtain(mLocationHandler, MESSAGE_HEARTBEAT, provider);
+ mLocationHandler.sendMessageAtTime(m, SystemClock.uptimeMillis() + 1000);
+ } else {
+ try {
+ // Notify the listener that updates are currently disabled
+ if (receiver.isListener()) {
+ receiver.getListener().onProviderDisabled(provider);
+ }
+ } catch(RemoteException e) {
+ Log.w(TAG, "RemoteException calling onProviderDisabled on " +
+ receiver.getListener());
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ public void removeUpdates(ILocationListener listener) {
+ try {
+ synchronized (mLocationListeners) {
+ removeUpdatesLocked(new Receiver(listener, Binder.getCallingUid()));
+ }
+ } catch (SecurityException se) {
+ throw se;
+ } catch (Exception e) {
+ Log.e(TAG, "removeUpdates got exception:", e);
+ }
+ }
+
+ public void removeUpdatesPI(PendingIntent intent) {
+ try {
+ synchronized (mLocationListeners) {
+ removeUpdatesLocked(new Receiver(intent, Binder.getCallingUid()));
+ }
+ } catch (SecurityException se) {
+ throw se;
+ } catch (Exception e) {
+ Log.e(TAG, "removeUpdates got exception:", e);
+ }
+ }
+
+ private void removeUpdatesLocked(Receiver receiver) {
+ if (Config.LOGD) {
+ Log.d(TAG, "_removeUpdates: listener = " + receiver);
+ }
+
+ // so wakelock calls will succeed
+ final int callingUid = Binder.getCallingUid();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ int idx = mListeners.indexOf(receiver);
+ if (idx >= 0) {
+ Receiver myReceiver = mListeners.remove(idx);
+ if (myReceiver.isListener()) {
+ myReceiver.getListener().asBinder().unlinkToDeath(myReceiver, 0);
+ }
+ }
+
+ // Record which providers were associated with this listener
+ HashSet<String> providers = new HashSet<String>();
+ HashMap<String,UpdateRecord> oldRecords = mLocationListeners.get(receiver);
+ if (oldRecords != null) {
+ // Call dispose() on the obsolete update records.
+ for (UpdateRecord record : oldRecords.values()) {
+ if (record.mProvider.equals(LocationManager.NETWORK_PROVIDER)) {
+ if (mNetworkLocationInterface != null) {
+ mNetworkLocationInterface.removeListener(record.mPackages);
+ }
+ }
+ record.disposeLocked();
+ }
+ // Accumulate providers
+ providers.addAll(oldRecords.keySet());
+ }
+
+ mLocationListeners.remove(receiver);
+ mLastFixBroadcast.remove(receiver);
+ mLastStatusBroadcast.remove(receiver);
+
+ // See if the providers associated with this listener have any
+ // other listeners; if one does, inform it of the new smallest minTime
+ // value; if one does not, disable location tracking for it
+ for (String provider : providers) {
+ // If provider is already disabled, don't need to do anything
+ if (!isAllowedBySettingsLocked(provider)) {
+ continue;
+ }
+
+ boolean hasOtherListener = false;
+ ArrayList<UpdateRecord> recordsForProvider = mRecordsByProvider.get(provider);
+ if (recordsForProvider != null && recordsForProvider.size() > 0) {
+ hasOtherListener = true;
+ }
+
+ LocationProviderImpl p = LocationProviderImpl.getProvider(provider);
+ if (p != null) {
+ if (hasOtherListener) {
+ p.setMinTime(getMinTimeLocked(provider));
+ } else {
+ mLocationHandler.removeMessages(MESSAGE_HEARTBEAT, provider);
+ p.enableLocationTracking(false);
+ }
+
+ if (p == mGpsLocationProvider && mGpsNavigating) {
+ updateReportedGpsLocked();
+ }
+ }
+ }
+
+ updateWakelockStatusLocked(mScreenOn);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ public boolean addGpsStatusListener(IGpsStatusListener listener) {
+ if (mGpsLocationProvider == null) {
+ return false;
+ }
+ if (mContext.checkCallingPermission(ACCESS_FINE_LOCATION) !=
+ PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires ACCESS_FINE_LOCATION permission");
+ }
+
+ try {
+ mGpsLocationProvider.addGpsStatusListener(listener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "RemoteException in addGpsStatusListener");
+ return false;
+ }
+ return true;
+ }
+
+ public void removeGpsStatusListener(IGpsStatusListener listener) {
+ synchronized (mLocationListeners) {
+ mGpsLocationProvider.removeGpsStatusListener(listener);
+ }
+ }
+
+ public boolean sendExtraCommand(String provider, String command, Bundle extras) {
+ // first check for permission to the provider
+ checkPermissionsSafe(provider);
+ // and check for ACCESS_LOCATION_EXTRA_COMMANDS
+ if ((mContext.checkCallingPermission(ACCESS_LOCATION_EXTRA_COMMANDS)
+ != PackageManager.PERMISSION_GRANTED)) {
+ throw new SecurityException("Requires ACCESS_LOCATION_EXTRA_COMMANDS permission");
+ }
+
+ synchronized (mLocationListeners) {
+ LocationProviderImpl impl = LocationProviderImpl.getProvider(provider);
+ if (provider == null) {
+ return false;
+ }
+
+ return impl.sendExtraCommand(command, extras);
+ }
+ }
+
+ class ProximityAlert {
+ final int mUid;
+ final double mLatitude;
+ final double mLongitude;
+ final float mRadius;
+ final long mExpiration;
+ final PendingIntent mIntent;
+ final Location mLocation;
+
+ public ProximityAlert(int uid, double latitude, double longitude,
+ float radius, long expiration, PendingIntent intent) {
+ mUid = uid;
+ mLatitude = latitude;
+ mLongitude = longitude;
+ mRadius = radius;
+ mExpiration = expiration;
+ mIntent = intent;
+
+ mLocation = new Location("");
+ mLocation.setLatitude(latitude);
+ mLocation.setLongitude(longitude);
+ }
+
+ long getExpiration() {
+ return mExpiration;
+ }
+
+ PendingIntent getIntent() {
+ return mIntent;
+ }
+
+ boolean isInProximity(double latitude, double longitude) {
+ Location loc = new Location("");
+ loc.setLatitude(latitude);
+ loc.setLongitude(longitude);
+
+ double radius = loc.distanceTo(mLocation);
+ return radius <= mRadius;
+ }
+
+ @Override
+ public String toString() {
+ return "ProximityAlert{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " uid " + mUid + mIntent + "}";
+ }
+
+ void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + this);
+ pw.println(prefix + "mLatitude=" + mLatitude + " mLongitude=" + mLongitude);
+ pw.println(prefix + "mRadius=" + mRadius + " mExpiration=" + mExpiration);
+ pw.println(prefix + "mIntent=" + mIntent);
+ pw.println(prefix + "mLocation:");
+ mLocation.dump(new PrintWriterPrinter(pw), prefix + " ");
+ }
+ }
+
+ // Listener for receiving locations to trigger proximity alerts
+ class ProximityListener extends ILocationListener.Stub {
+
+ boolean isGpsAvailable = false;
+
+ // Note: this is called with the lock held.
+ public void onLocationChanged(Location loc) {
+
+ // If Gps is available, then ignore updates from NetworkLocationProvider
+ if (loc.getProvider().equals(LocationManager.GPS_PROVIDER)) {
+ isGpsAvailable = true;
+ }
+ if (isGpsAvailable && loc.getProvider().equals(LocationManager.NETWORK_PROVIDER)) {
+ return;
+ }
+
+ // Process proximity alerts
+ long now = System.currentTimeMillis();
+ double latitude = loc.getLatitude();
+ double longitude = loc.getLongitude();
+ ArrayList<PendingIntent> intentsToRemove = null;
+
+ for (ProximityAlert alert : mProximityAlerts.values()) {
+ PendingIntent intent = alert.getIntent();
+ long expiration = alert.getExpiration();
+
+ if ((expiration == -1) || (now <= expiration)) {
+ boolean entered = mProximitiesEntered.contains(alert);
+ boolean inProximity =
+ alert.isInProximity(latitude, longitude);
+ if (!entered && inProximity) {
+ if (Config.LOGD) {
+ Log.i(TAG, "Entered alert");
+ }
+ mProximitiesEntered.add(alert);
+ Intent enteredIntent = new Intent();
+ enteredIntent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, true);
+ try {
+ intent.send(mContext, 0, enteredIntent, null, null);
+ } catch (PendingIntent.CanceledException e) {
+ if (Config.LOGD) {
+ Log.i(TAG, "Canceled proximity alert: " + alert, e);
+ }
+ if (intentsToRemove == null) {
+ intentsToRemove = new ArrayList<PendingIntent>();
+ }
+ intentsToRemove.add(intent);
+ }
+ } else if (entered && !inProximity) {
+ if (Config.LOGD) {
+ Log.i(TAG, "Exited alert");
+ }
+ mProximitiesEntered.remove(alert);
+ Intent exitedIntent = new Intent();
+ exitedIntent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, false);
+ try {
+ intent.send(mContext, 0, exitedIntent, null, null);
+ } catch (PendingIntent.CanceledException e) {
+ if (Config.LOGD) {
+ Log.i(TAG, "Canceled proximity alert: " + alert, e);
+ }
+ if (intentsToRemove == null) {
+ intentsToRemove = new ArrayList<PendingIntent>();
+ }
+ intentsToRemove.add(intent);
+ }
+ }
+ } else {
+ // Mark alert for expiration
+ if (Config.LOGD) {
+ Log.i(TAG, "Expiring proximity alert: " + alert);
+ }
+ if (intentsToRemove == null) {
+ intentsToRemove = new ArrayList<PendingIntent>();
+ }
+ intentsToRemove.add(alert.getIntent());
+ }
+ }
+
+ // Remove expired alerts
+ if (intentsToRemove != null) {
+ for (PendingIntent i : intentsToRemove) {
+ mProximityAlerts.remove(i);
+ ProximityAlert alert = mProximityAlerts.get(i);
+ mProximitiesEntered.remove(alert);
+ }
+ }
+
+ }
+
+ // Note: this is called with the lock held.
+ public void onProviderDisabled(String provider) {
+ if (provider.equals(LocationManager.GPS_PROVIDER)) {
+ isGpsAvailable = false;
+ }
+ }
+
+ // Note: this is called with the lock held.
+ public void onProviderEnabled(String provider) {
+ // ignore
+ }
+
+ // Note: this is called with the lock held.
+ public void onStatusChanged(String provider, int status, Bundle extras) {
+ if ((provider.equals(LocationManager.GPS_PROVIDER)) &&
+ (status != LocationProvider.AVAILABLE)) {
+ isGpsAvailable = false;
+ }
+ }
+ }
+
+ public void addProximityAlert(double latitude, double longitude,
+ float radius, long expiration, PendingIntent intent) {
+ try {
+ synchronized (mLocationListeners) {
+ addProximityAlertLocked(latitude, longitude, radius, expiration, intent);
+ }
+ } catch (SecurityException se) {
+ throw se;
+ } catch (Exception e) {
+ Log.e(TAG, "addProximityAlert got exception:", e);
+ }
+ }
+
+ private void addProximityAlertLocked(double latitude, double longitude,
+ float radius, long expiration, PendingIntent intent) {
+ if (Config.LOGD) {
+ Log.d(TAG, "addProximityAlert: latitude = " + latitude +
+ ", longitude = " + longitude +
+ ", expiration = " + expiration +
+ ", intent = " + intent);
+ }
+
+ // Require ability to access all providers for now
+ if (!isAllowedProviderSafe(LocationManager.GPS_PROVIDER) ||
+ !isAllowedProviderSafe(LocationManager.NETWORK_PROVIDER)) {
+ throw new SecurityException("Requires ACCESS_FINE_LOCATION permission");
+ }
+
+ if (expiration != -1) {
+ expiration += System.currentTimeMillis();
+ }
+ ProximityAlert alert = new ProximityAlert(Binder.getCallingUid(),
+ latitude, longitude, radius, expiration, intent);
+ mProximityAlerts.put(intent, alert);
+
+ if (mProximityListener == null) {
+ mProximityListener = new Receiver(new ProximityListener(), -1);
+
+ LocationProvider provider = LocationProviderImpl.getProvider(
+ LocationManager.GPS_PROVIDER);
+ if (provider != null) {
+ requestLocationUpdatesLocked(provider.getName(), 1000L, 1.0f, mProximityListener);
+ }
+
+ provider =
+ LocationProviderImpl.getProvider(LocationManager.NETWORK_PROVIDER);
+ if (provider != null) {
+ requestLocationUpdatesLocked(provider.getName(), 1000L, 1.0f, mProximityListener);
+ }
+ } else if (mGpsNavigating) {
+ updateReportedGpsLocked();
+ }
+ }
+
+ public void removeProximityAlert(PendingIntent intent) {
+ try {
+ synchronized (mLocationListeners) {
+ removeProximityAlertLocked(intent);
+ }
+ } catch (SecurityException se) {
+ throw se;
+ } catch (Exception e) {
+ Log.e(TAG, "removeProximityAlert got exception:", e);
+ }
+ }
+
+ private void removeProximityAlertLocked(PendingIntent intent) {
+ if (Config.LOGD) {
+ Log.d(TAG, "removeProximityAlert: intent = " + intent);
+ }
+
+ mProximityAlerts.remove(intent);
+ if (mProximityAlerts.size() == 0) {
+ removeUpdatesLocked(mProximityListener);
+ mProximityListener = null;
+ } else if (mGpsNavigating) {
+ updateReportedGpsLocked();
+ }
+ }
+
+ /**
+ * @return null if the provider does not exits
+ * @throw SecurityException if the provider is not allowed to be
+ * accessed by the caller
+ */
+ public Bundle getProviderInfo(String provider) {
+ try {
+ synchronized (mLocationListeners) {
+ return _getProviderInfoLocked(provider);
+ }
+ } catch (SecurityException se) {
+ throw se;
+ } catch (Exception e) {
+ Log.e(TAG, "_getProviderInfo got exception:", e);
+ return null;
+ }
+ }
+
+ private Bundle _getProviderInfoLocked(String provider) {
+ LocationProviderImpl p = LocationProviderImpl.getProvider(provider);
+ if (p == null) {
+ return null;
+ }
+
+ checkPermissionsSafe(provider);
+
+ Bundle b = new Bundle();
+ b.putBoolean("network", p.requiresNetwork());
+ b.putBoolean("satellite", p.requiresSatellite());
+ b.putBoolean("cell", p.requiresCell());
+ b.putBoolean("cost", p.hasMonetaryCost());
+ b.putBoolean("altitude", p.supportsAltitude());
+ b.putBoolean("speed", p.supportsSpeed());
+ b.putBoolean("bearing", p.supportsBearing());
+ b.putInt("power", p.getPowerRequirement());
+ b.putInt("accuracy", p.getAccuracy());
+
+ return b;
+ }
+
+ public boolean isProviderEnabled(String provider) {
+ try {
+ synchronized (mLocationListeners) {
+ return _isProviderEnabledLocked(provider);
+ }
+ } catch (SecurityException se) {
+ throw se;
+ } catch (Exception e) {
+ Log.e(TAG, "isProviderEnabled got exception:", e);
+ return false;
+ }
+ }
+
+ private boolean _isProviderEnabledLocked(String provider) {
+ checkPermissionsSafe(provider);
+
+ LocationProviderImpl p = LocationProviderImpl.getProvider(provider);
+ if (p == null) {
+ throw new IllegalArgumentException("provider=" + provider);
+ }
+ return isAllowedBySettingsLocked(provider);
+ }
+
+ public Location getLastKnownLocation(String provider) {
+ try {
+ synchronized (mLocationListeners) {
+ return _getLastKnownLocationLocked(provider);
+ }
+ } catch (SecurityException se) {
+ throw se;
+ } catch (Exception e) {
+ Log.e(TAG, "getLastKnownLocation got exception:", e);
+ return null;
+ }
+ }
+
+ private Location _getLastKnownLocationLocked(String provider) {
+ checkPermissionsSafe(provider);
+
+ LocationProviderImpl p = LocationProviderImpl.getProvider(provider);
+ if (p == null) {
+ throw new IllegalArgumentException("provider=" + provider);
+ }
+
+ if (!isAllowedBySettingsLocked(provider)) {
+ return null;
+ }
+
+ Location location = mLastKnownLocation.get(provider);
+ if (location == null) {
+ // Get the persistent last known location for the provider
+ location = readLastKnownLocationLocked(provider);
+ if (location != null) {
+ mLastKnownLocation.put(provider, location);
+ }
+ }
+
+ return location;
+ }
+
+ private static boolean shouldBroadcastSafe(Location loc, Location lastLoc, UpdateRecord record) {
+ // Always broadcast the first update
+ if (lastLoc == null) {
+ return true;
+ }
+
+ // Don't broadcast same location again regardless of condition
+ // TODO - we should probably still rebroadcast if user explicitly sets a minTime > 0
+ if (loc.getTime() == lastLoc.getTime()) {
+ return false;
+ }
+
+ // Check whether sufficient distance has been traveled
+ double minDistance = record.mMinDistance;
+ if (minDistance > 0.0) {
+ if (loc.distanceTo(lastLoc) <= minDistance) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void handleLocationChangedLocked(String provider) {
+ ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
+ if (records == null || records.size() == 0) {
+ return;
+ }
+
+ LocationProviderImpl p = LocationProviderImpl.getProvider(provider);
+ if (p == null) {
+ return;
+ }
+
+ // Get location object
+ Location loc = mLocationsByProvider.get(provider);
+ if (loc == null) {
+ loc = new Location(provider);
+ mLocationsByProvider.put(provider, loc);
+ } else {
+ loc.reset();
+ }
+
+ // Use the mock location if available
+ Location mockLoc = mMockProviderLocation.get(provider);
+ boolean locationValid;
+ if (mockLoc != null) {
+ locationValid = true;
+ loc.set(mockLoc);
+ } else {
+ locationValid = p.getLocation(loc);
+ }
+
+ // Update last known location for provider
+ if (locationValid) {
+ Location location = mLastKnownLocation.get(provider);
+ if (location == null) {
+ mLastKnownLocation.put(provider, new Location(loc));
+ } else {
+ location.set(loc);
+ }
+ writeLastKnownLocationLocked(provider, loc);
+
+ if (p instanceof INetworkLocationProvider) {
+ mWakeLockNetworkReceived = true;
+ } else if (p instanceof GpsLocationProvider) {
+ // Gps location received signal is in NetworkStateBroadcastReceiver
+ }
+ }
+
+ // Fetch latest status update time
+ long newStatusUpdateTime = p.getStatusUpdateTime();
+
+ // Override real time with mock time if present
+ Long mockStatusUpdateTime = mMockProviderStatusUpdateTime.get(provider);
+ if (mockStatusUpdateTime != null) {
+ newStatusUpdateTime = mockStatusUpdateTime.longValue();
+ }
+
+ // Get latest status
+ Bundle extras = new Bundle();
+ int status = p.getStatus(extras);
+
+ // Override status with mock status if present
+ Integer mockStatus = mMockProviderStatus.get(provider);
+ if (mockStatus != null) {
+ status = mockStatus.intValue();
+ }
+
+ // Override extras with mock extras if present
+ Bundle mockExtras = mMockProviderStatusExtras.get(provider);
+ if (mockExtras != null) {
+ extras.clear();
+ extras.putAll(mockExtras);
+ }
+
+ ArrayList<Receiver> deadReceivers = null;
+
+ // Broadcast location or status to all listeners
+ final int N = records.size();
+ for (int i=0; i<N; i++) {
+ UpdateRecord r = records.get(i);
+ Receiver receiver = r.mReceiver;
+
+ // Broadcast location only if it is valid
+ if (locationValid) {
+ HashMap<String,Location> map = mLastFixBroadcast.get(receiver);
+ if (map == null) {
+ map = new HashMap<String,Location>();
+ mLastFixBroadcast.put(receiver, map);
+ }
+ Location lastLoc = map.get(provider);
+ if ((lastLoc == null) || shouldBroadcastSafe(loc, lastLoc, r)) {
+ if (lastLoc == null) {
+ lastLoc = new Location(loc);
+ map.put(provider, lastLoc);
+ } else {
+ lastLoc.set(loc);
+ }
+ if (!receiver.callLocationChangedLocked(loc)) {
+ Log.w(TAG, "RemoteException calling onLocationChanged on " + receiver);
+ if (deadReceivers == null) {
+ deadReceivers = new ArrayList<Receiver>();
+ }
+ deadReceivers.add(receiver);
+ }
+ }
+ }
+
+ // Broadcast status message
+ HashMap<String,Long> statusMap = mLastStatusBroadcast.get(receiver);
+ if (statusMap == null) {
+ statusMap = new HashMap<String,Long>();
+ mLastStatusBroadcast.put(receiver, statusMap);
+ }
+ long prevStatusUpdateTime =
+ (statusMap.get(provider) != null) ? statusMap.get(provider) : 0;
+
+ if ((newStatusUpdateTime > prevStatusUpdateTime) &&
+ (prevStatusUpdateTime != 0 || status != LocationProvider.AVAILABLE)) {
+
+ statusMap.put(provider, newStatusUpdateTime);
+ if (!receiver.callStatusChangedLocked(provider, status, extras)) {
+ Log.w(TAG, "RemoteException calling onStatusChanged on " + receiver);
+ if (deadReceivers == null) {
+ deadReceivers = new ArrayList<Receiver>();
+ }
+ if (!deadReceivers.contains(receiver)) {
+ deadReceivers.add(receiver);
+ }
+ }
+ }
+ }
+
+ if (deadReceivers != null) {
+ for (int i=deadReceivers.size()-1; i>=0; i--) {
+ removeUpdatesLocked(deadReceivers.get(i));
+ }
+ }
+ }
+
+ private class LocationWorkerHandler extends Handler {
+
+ @Override
+ public void handleMessage(Message msg) {
+ try {
+ if (msg.what == MESSAGE_HEARTBEAT) {
+ // log("LocationWorkerHandler: Heartbeat!");
+
+ synchronized (mLocationListeners) {
+ String provider = (String) msg.obj;
+ if (!isAllowedBySettingsLocked(provider)) {
+ return;
+ }
+
+ // Process the location fix if the screen is on or we're holding a wakelock
+ if (mScreenOn || (mWakeLockAcquireTime != 0)) {
+ handleLocationChangedLocked(provider);
+ }
+
+ // If it continues to have listeners
+ ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
+ if (records != null && records.size() > 0) {
+ Message m = Message.obtain(this, MESSAGE_HEARTBEAT, provider);
+ sendMessageAtTime(m, SystemClock.uptimeMillis() + 1000);
+ }
+
+ if ((mWakeLockAcquireTime != 0) &&
+ (SystemClock.elapsedRealtime() - mWakeLockAcquireTime
+ > MAX_TIME_FOR_WAKE_LOCK)) {
+
+ removeMessages(MESSAGE_ACQUIRE_WAKE_LOCK);
+ removeMessages(MESSAGE_RELEASE_WAKE_LOCK);
+
+ log("LocationWorkerHandler: Exceeded max time for wake lock");
+ Message m = Message.obtain(this, MESSAGE_RELEASE_WAKE_LOCK);
+ sendMessageAtFrontOfQueue(m);
+
+ } else if (mWakeLockAcquireTime != 0 &&
+ mWakeLockGpsReceived && mWakeLockNetworkReceived) {
+
+ removeMessages(MESSAGE_ACQUIRE_WAKE_LOCK);
+ removeMessages(MESSAGE_RELEASE_WAKE_LOCK);
+
+ log("LocationWorkerHandler: Locations received.");
+ mWakeLockAcquireTime = 0;
+ Message m = Message.obtain(this, MESSAGE_RELEASE_WAKE_LOCK);
+ sendMessageDelayed(m, TIME_AFTER_WAKE_LOCK);
+ }
+ }
+
+ } else if (msg.what == MESSAGE_ACQUIRE_WAKE_LOCK) {
+ log("LocationWorkerHandler: Acquire");
+ synchronized (mLocationListeners) {
+ acquireWakeLockLocked();
+ }
+ } else if (msg.what == MESSAGE_RELEASE_WAKE_LOCK) {
+ log("LocationWorkerHandler: Release");
+
+ // Update wakelock status so the next alarm is set before releasing wakelock
+ synchronized (mLocationListeners) {
+ updateWakelockStatusLocked(mScreenOn);
+ releaseWakeLockLocked();
+ }
+ } else if (msg.what == MESSAGE_INSTALL_NETWORK_LOCATION_PROVIDER) {
+ synchronized (mLocationListeners) {
+ Log.d(TAG, "installing network location provider");
+ INetworkLocationManager.InstallCallback callback =
+ (INetworkLocationManager.InstallCallback)msg.obj;
+ callback.installNetworkLocationProvider(LocationManagerService.this);
+ }
+ }
+ } catch (Exception e) {
+ // Log, don't crash!
+ Log.e(TAG, "Exception in LocationWorkerHandler.handleMessage:", e);
+ }
+ }
+ }
+
+ class CellLocationUpdater extends Thread {
+ CellLocation mNextLocation;
+
+ CellLocationUpdater() {
+ super("CellLocationUpdater");
+ }
+
+ @Override
+ public void run() {
+ int curAsu = -1;
+ CellLocation curLocation = null;
+
+ while (true) {
+ // See if there is more work to do...
+ synchronized (mLocationListeners) {
+ if (curLocation == mNextLocation) {
+ mCellLocationUpdater = null;
+ break;
+ }
+
+ curLocation = mNextLocation;
+ if (curLocation == null) {
+ mCellLocationUpdater = null;
+ break;
+ }
+
+ curAsu = mLastSignalStrength;
+
+ mNextLocation = null;
+ }
+
+ try {
+ // Gets cell state. This can block so must be done without
+ // locks held.
+ CellState cs = new CellState(mTelephonyManager, curLocation, curAsu);
+
+ synchronized (mLocationListeners) {
+ mLastCellState = cs;
+
+ cs.updateSignalStrength(mLastSignalStrength);
+ cs.updateRadioType(mLastRadioType);
+
+ // Notify collector
+ if (mCollector != null) {
+ mCollector.updateCellState(cs);
+ }
+
+ // Updates providers
+ List<LocationProviderImpl> providers = LocationProviderImpl.getProviders();
+ for (LocationProviderImpl provider : providers) {
+ if (provider.requiresCell()) {
+ provider.updateCellState(cs);
+ }
+ }
+ }
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Exception in PhoneStateListener.onCellLocationChanged:", e);
+ }
+ }
+ }
+ }
+
+ CellLocationUpdater mCellLocationUpdater = null;
+ CellState mLastCellState = null;
+ int mLastSignalStrength = -1;
+ int mLastRadioType = -1;
+
+ PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+
+ @Override
+ public void onCellLocationChanged(CellLocation cellLocation) {
+ synchronized (mLocationListeners) {
+ if (mCellLocationUpdater == null) {
+ mCellLocationUpdater = new CellLocationUpdater();
+ mCellLocationUpdater.start();
+ }
+ mCellLocationUpdater.mNextLocation = cellLocation;
+ }
+ }
+
+ @Override
+ public void onSignalStrengthChanged(int asu) {
+ synchronized (mLocationListeners) {
+ mLastSignalStrength = asu;
+
+ if (mLastCellState != null) {
+ mLastCellState.updateSignalStrength(asu);
+ }
+ }
+ }
+
+ @Override
+ public void onDataConnectionStateChanged(int state) {
+ synchronized (mLocationListeners) {
+ // Get radio type
+ int radioType = mTelephonyManager.getNetworkType();
+ if (radioType == TelephonyManager.NETWORK_TYPE_GPRS ||
+ radioType == TelephonyManager.NETWORK_TYPE_EDGE) {
+ radioType = CellState.RADIO_TYPE_GPRS;
+ } else if (radioType == TelephonyManager.NETWORK_TYPE_UMTS) {
+ radioType = CellState.RADIO_TYPE_WCDMA;
+ }
+ mLastRadioType = radioType;
+
+ if (mLastCellState != null) {
+ mLastCellState.updateRadioType(radioType);
+ }
+ }
+ }
+ };
+
+ private class PowerStateBroadcastReceiver extends BroadcastReceiver {
+ @Override public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ if (action.equals(ALARM_INTENT)) {
+ synchronized (mLocationListeners) {
+ log("PowerStateBroadcastReceiver: Alarm received");
+ mLocationHandler.removeMessages(MESSAGE_ACQUIRE_WAKE_LOCK);
+ // Have to do this immediately, rather than posting a
+ // message, so we execute our code while the system
+ // is holding a wake lock until the alarm broadcast
+ // is finished.
+ acquireWakeLockLocked();
+ }
+
+ } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+ log("PowerStateBroadcastReceiver: Screen off");
+ synchronized (mLocationListeners) {
+ updateWakelockStatusLocked(false);
+ }
+
+ } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
+ log("PowerStateBroadcastReceiver: Screen on");
+ synchronized (mLocationListeners) {
+ updateWakelockStatusLocked(true);
+ }
+
+ } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
+ log("PowerStateBroadcastReceiver: Battery changed");
+ synchronized (mLocationListeners) {
+ int scale = intent.getIntExtra(BATTERY_EXTRA_SCALE, 100);
+ int level = intent.getIntExtra(BATTERY_EXTRA_LEVEL, 0);
+ boolean plugged = intent.getIntExtra(BATTERY_EXTRA_PLUGGED, 0) != 0;
+
+ // Notify collector battery state
+ if (mCollector != null) {
+ mCollector.updateBatteryState(scale, level, plugged);
+ }
+ }
+ } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
+ || action.equals(Intent.ACTION_PACKAGE_RESTARTED)) {
+ synchronized (mLocationListeners) {
+ int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+ if (uid >= 0) {
+ ArrayList<Receiver> removedRecs = null;
+ for (ArrayList<UpdateRecord> i : mRecordsByProvider.values()) {
+ for (int j=i.size()-1; j>=0; j--) {
+ UpdateRecord ur = i.get(j);
+ if (ur.mReceiver.isPendingIntent() && ur.mUid == uid) {
+ if (removedRecs == null) {
+ removedRecs = new ArrayList<Receiver>();
+ }
+ if (!removedRecs.contains(ur.mReceiver)) {
+ removedRecs.add(ur.mReceiver);
+ }
+ }
+ }
+ }
+ ArrayList<ProximityAlert> removedAlerts = null;
+ for (ProximityAlert i : mProximityAlerts.values()) {
+ if (i.mUid == uid) {
+ if (removedAlerts == null) {
+ removedAlerts = new ArrayList<ProximityAlert>();
+ }
+ if (!removedAlerts.contains(i)) {
+ removedAlerts.add(i);
+ }
+ }
+ }
+ if (removedRecs != null) {
+ for (int i=removedRecs.size()-1; i>=0; i--) {
+ removeUpdatesLocked(removedRecs.get(i));
+ }
+ }
+ if (removedAlerts != null) {
+ for (int i=removedAlerts.size()-1; i>=0; i--) {
+ removeProximityAlertLocked(removedAlerts.get(i).mIntent);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private class NetworkStateBroadcastReceiver extends BroadcastReceiver {
+ @Override public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+
+ List<ScanResult> wifiScanResults = mWifiManager.getScanResults();
+
+ if (wifiScanResults == null) {
+ return;
+ }
+
+ // Notify provider and collector of Wifi scan results
+ synchronized (mLocationListeners) {
+ if (mCollector != null) {
+ mCollector.updateWifiScanResults(wifiScanResults);
+ }
+ if (mNetworkLocationInterface != null) {
+ mNetworkLocationInterface.updateWifiScanResults(wifiScanResults);
+ }
+ }
+
+ } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+ int networkState = LocationProvider.TEMPORARILY_UNAVAILABLE;
+
+ boolean noConnectivity =
+ intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
+ if (!noConnectivity) {
+ networkState = LocationProvider.AVAILABLE;
+ }
+
+ // Notify location providers of current network state
+ synchronized (mLocationListeners) {
+ List<LocationProviderImpl> providers = LocationProviderImpl.getProviders();
+ for (LocationProviderImpl provider : providers) {
+ if (provider.requiresNetwork()) {
+ provider.updateNetworkState(networkState);
+ }
+ }
+ }
+
+ } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+ int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+ WifiManager.WIFI_STATE_UNKNOWN);
+
+ boolean enabled;
+ if (state == WifiManager.WIFI_STATE_ENABLED) {
+ enabled = true;
+ } else if (state == WifiManager.WIFI_STATE_DISABLED) {
+ enabled = false;
+ } else {
+ return;
+ }
+
+ // Notify network provider of current wifi enabled state
+ synchronized (mLocationListeners) {
+ if (mNetworkLocationInterface != null) {
+ mNetworkLocationInterface.updateWifiEnabledState(enabled);
+ }
+ }
+
+ } else if (action.equals(GpsLocationProvider.GPS_ENABLED_CHANGE_ACTION)) {
+
+ final boolean enabled = intent.getBooleanExtra(GpsLocationProvider.EXTRA_ENABLED,
+ false);
+
+ synchronized (mLocationListeners) {
+ if (enabled) {
+ updateReportedGpsLocked();
+ mGpsNavigating = true;
+ } else {
+ reportStopGpsLocked();
+ mGpsNavigating = false;
+ // When GPS is disabled, we are OK to release wake-lock
+ mWakeLockGpsReceived = true;
+ }
+ }
+ }
+
+ }
+ }
+
+ // Wake locks
+
+ private void updateWakelockStatusLocked(boolean screenOn) {
+ log("updateWakelockStatus(): " + screenOn);
+
+ boolean needsLock = false;
+ long minTime = Integer.MAX_VALUE;
+
+ if (mNetworkLocationProvider != null && mNetworkLocationProvider.isLocationTracking()) {
+ needsLock = true;
+ minTime = Math.min(mNetworkLocationProvider.getMinTime(), minTime);
+ }
+
+ if (mGpsLocationProvider != null && mGpsLocationProvider.isLocationTracking()) {
+ needsLock = true;
+ minTime = Math.min(mGpsLocationProvider.getMinTime(), minTime);
+ if (screenOn) {
+ startGpsLocked();
+ } else if (mScreenOn && !screenOn) {
+ // We just turned the screen off so stop navigating
+ stopGpsLocked();
+ }
+ }
+
+ mScreenOn = screenOn;
+
+ PendingIntent sender =
+ PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_INTENT), 0);
+
+ // Cancel existing alarm
+ log("Cancelling existing alarm");
+ mAlarmManager.cancel(sender);
+
+ if (needsLock && !mScreenOn) {
+ long now = SystemClock.elapsedRealtime();
+ mAlarmManager.set(
+ AlarmManager.ELAPSED_REALTIME_WAKEUP, now + minTime, sender);
+ mAlarmInterval = minTime;
+ log("Creating a new wakelock alarm with minTime = " + minTime);
+ } else {
+ log("No need for alarm");
+ mAlarmInterval = -1;
+
+ // Clear out existing wakelocks
+ mLocationHandler.removeMessages(MESSAGE_ACQUIRE_WAKE_LOCK);
+ mLocationHandler.removeMessages(MESSAGE_RELEASE_WAKE_LOCK);
+ releaseWakeLockLocked();
+ }
+ }
+
+ private void acquireWakeLockLocked() {
+ try {
+ acquireWakeLockXLocked();
+ } catch (Exception e) {
+ // This is to catch a runtime exception thrown when we try to release an
+ // already released lock.
+ Log.e(TAG, "exception in acquireWakeLock()", e);
+ }
+ }
+
+ private void acquireWakeLockXLocked() {
+ if (mWakeLock.isHeld()) {
+ log("Must release wakelock before acquiring");
+ mWakeLockAcquireTime = 0;
+ mWakeLock.release();
+ }
+
+ boolean networkActive = (mNetworkLocationProvider != null)
+ && mNetworkLocationProvider.isLocationTracking();
+ boolean gpsActive = (mGpsLocationProvider != null)
+ && mGpsLocationProvider.isLocationTracking();
+
+ boolean needsLock = networkActive || gpsActive;
+ if (!needsLock) {
+ log("No need for Lock!");
+ return;
+ }
+
+ mWakeLockGpsReceived = !gpsActive;
+ mWakeLockNetworkReceived = !networkActive;
+
+ // Acquire wake lock
+ mWakeLock.acquire();
+ mWakeLockAcquireTime = SystemClock.elapsedRealtime();
+ log("Acquired wakelock");
+
+ // Start the gps provider
+ startGpsLocked();
+
+ // Acquire cell lock
+ if (mCellWakeLockAcquired) {
+ // Lock is already acquired
+ } else if (!mWakeLockNetworkReceived) {
+ mTelephonyManager.enableLocationUpdates();
+ mCellWakeLockAcquired = true;
+ } else {
+ mCellWakeLockAcquired = false;
+ }
+
+ // Notify NetworkLocationProvider
+ if (mNetworkLocationInterface != null) {
+ mNetworkLocationInterface.updateCellLockStatus(mCellWakeLockAcquired);
+ }
+
+ // Acquire wifi lock
+ WifiManager.WifiLock wifiLock = getWifiWakelockLocked();
+ if (wifiLock != null) {
+ if (mWifiWakeLockAcquired) {
+ // Lock is already acquired
+ } else if (mWifiManager.isWifiEnabled() && !mWakeLockNetworkReceived) {
+ wifiLock.acquire();
+ mWifiWakeLockAcquired = true;
+ } else {
+ mWifiWakeLockAcquired = false;
+ Log.w(TAG, "acquireWakeLock(): Unable to get WiFi lock");
+ }
+ }
+ }
+
+ private boolean reportGpsUidLocked(int curSeq, int nextSeq, int uid) {
+ int seq = mReportedGpsUids.get(uid, -1);
+ if (seq == curSeq) {
+ // Already reported; propagate to next sequence.
+ mReportedGpsUids.put(uid, nextSeq);
+ return true;
+ } else if (seq != nextSeq) {
+ try {
+ // New UID; report it.
+ mBatteryStats.noteStartGps(uid);
+ mReportedGpsUids.put(uid, nextSeq);
+ return true;
+ } catch (RemoteException e) {
+ }
+ }
+ return false;
+ }
+
+ private void updateReportedGpsLocked() {
+ if (mGpsLocationProvider == null) {
+ return;
+ }
+
+ final String name = mGpsLocationProvider.getName();
+ final int curSeq = mReportedGpsSeq;
+ final int nextSeq = (curSeq+1) >= 0 ? (curSeq+1) : 0;
+ mReportedGpsSeq = nextSeq;
+
+ ArrayList<UpdateRecord> urs = mRecordsByProvider.get(name);
+ int num = 0;
+ final int N = urs.size();
+ for (int i=0; i<N; i++) {
+ UpdateRecord ur = urs.get(i);
+ if (ur.mReceiver == mProximityListener) {
+ // We don't want the system to take the blame for this one.
+ continue;
+ }
+ if (reportGpsUidLocked(curSeq, nextSeq, ur.mUid)) {
+ num++;
+ }
+ }
+
+ for (ProximityAlert pe : mProximityAlerts.values()) {
+ if (reportGpsUidLocked(curSeq, nextSeq, pe.mUid)) {
+ num++;
+ }
+ }
+
+ if (num != mReportedGpsUids.size()) {
+ // The number of uids is processed is different than the
+ // array; report any that are no longer active.
+ for (int i=mReportedGpsUids.size()-1; i>=0; i--) {
+ if (mReportedGpsUids.valueAt(i) != nextSeq) {
+ try {
+ mBatteryStats.noteStopGps(mReportedGpsUids.keyAt(i));
+ } catch (RemoteException e) {
+ }
+ mReportedGpsUids.removeAt(i);
+ }
+ }
+ }
+ }
+
+ private void reportStopGpsLocked() {
+ int curSeq = mReportedGpsSeq;
+ for (int i=mReportedGpsUids.size()-1; i>=0; i--) {
+ if (mReportedGpsUids.valueAt(i) == curSeq) {
+ try {
+ mBatteryStats.noteStopGps(mReportedGpsUids.keyAt(i));
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ curSeq++;
+ if (curSeq < 0) curSeq = 0;
+ mReportedGpsSeq = curSeq;
+ mReportedGpsUids.clear();
+ }
+
+ private void startGpsLocked() {
+ boolean gpsActive = (mGpsLocationProvider != null)
+ && mGpsLocationProvider.isLocationTracking();
+ if (gpsActive) {
+ mGpsLocationProvider.startNavigating();
+ }
+ }
+
+ private void stopGpsLocked() {
+ boolean gpsActive = mGpsLocationProvider != null
+ && mGpsLocationProvider.isLocationTracking();
+ if (gpsActive) {
+ mGpsLocationProvider.stopNavigating();
+ }
+ }
+
+ private void releaseWakeLockLocked() {
+ try {
+ releaseWakeLockXLocked();
+ } catch (Exception e) {
+ // This is to catch a runtime exception thrown when we try to release an
+ // already released lock.
+ Log.e(TAG, "exception in releaseWakeLock()", e);
+ }
+ }
+
+ private void releaseWakeLockXLocked() {
+ // Release wifi lock
+ WifiManager.WifiLock wifiLock = getWifiWakelockLocked();
+ if (wifiLock != null) {
+ if (mWifiWakeLockAcquired) {
+ wifiLock.release();
+ mWifiWakeLockAcquired = false;
+ }
+ }
+
+ if (!mScreenOn) {
+ // Stop the gps
+ stopGpsLocked();
+ }
+
+ // Release cell lock
+ if (mCellWakeLockAcquired) {
+ mTelephonyManager.disableLocationUpdates();
+ mCellWakeLockAcquired = false;
+ }
+
+ // Notify NetworkLocationProvider
+ if (mNetworkLocationInterface != null) {
+ mNetworkLocationInterface.updateCellLockStatus(mCellWakeLockAcquired);
+ }
+
+ // Release wake lock
+ mWakeLockAcquireTime = 0;
+ if (mWakeLock.isHeld()) {
+ log("Released wakelock");
+ mWakeLock.release();
+ } else {
+ log("Can't release wakelock again!");
+ }
+ }
+
+ // Geocoder
+
+ public String getFromLocation(double latitude, double longitude, int maxResults,
+ String language, String country, String variant, String appName, List<Address> addrs) {
+ synchronized (mLocationListeners) {
+ if (mNetworkLocationInterface != null) {
+ return mNetworkLocationInterface.getFromLocation(latitude, longitude, maxResults,
+ language, country, variant, appName, addrs);
+ } else {
+ return null;
+ }
+ }
+ }
+
+ public String getFromLocationName(String locationName,
+ double lowerLeftLatitude, double lowerLeftLongitude,
+ double upperRightLatitude, double upperRightLongitude, int maxResults,
+ String language, String country, String variant, String appName, List<Address> addrs) {
+ synchronized (mLocationListeners) {
+ if (mNetworkLocationInterface != null) {
+ return mNetworkLocationInterface.getFromLocationName(locationName, lowerLeftLatitude,
+ lowerLeftLongitude, upperRightLatitude, upperRightLongitude, maxResults,
+ language, country, variant, appName, addrs);
+ } else {
+ return null;
+ }
+ }
+ }
+
+ // Mock Providers
+
+ class MockProvider extends LocationProviderImpl {
+ boolean mRequiresNetwork;
+ boolean mRequiresSatellite;
+ boolean mRequiresCell;
+ boolean mHasMonetaryCost;
+ boolean mSupportsAltitude;
+ boolean mSupportsSpeed;
+ boolean mSupportsBearing;
+ int mPowerRequirement;
+ int mAccuracy;
+
+ public MockProvider(String name, boolean requiresNetwork, boolean requiresSatellite,
+ boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude,
+ boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy) {
+ super(name);
+
+ mRequiresNetwork = requiresNetwork;
+ mRequiresSatellite = requiresSatellite;
+ mRequiresCell = requiresCell;
+ mHasMonetaryCost = hasMonetaryCost;
+ mSupportsAltitude = supportsAltitude;
+ mSupportsBearing = supportsBearing;
+ mSupportsSpeed = supportsSpeed;
+ mPowerRequirement = powerRequirement;
+ mAccuracy = accuracy;
+ }
+
+ @Override
+ public void disable() {
+ String name = getName();
+ // We shouldn't normally need to lock, since this should only be called
+ // by the service with the lock held, but let's be paranid.
+ synchronized (mLocationListeners) {
+ mEnabledProviders.remove(name);
+ mDisabledProviders.add(name);
+ }
+ }
+
+ @Override
+ public void enable() {
+ String name = getName();
+ // We shouldn't normally need to lock, since this should only be called
+ // by the service with the lock held, but let's be paranid.
+ synchronized (mLocationListeners) {
+ mEnabledProviders.add(name);
+ mDisabledProviders.remove(name);
+ }
+ }
+
+ @Override
+ public boolean getLocation(Location l) {
+ // We shouldn't normally need to lock, since this should only be called
+ // by the service with the lock held, but let's be paranid.
+ synchronized (mLocationListeners) {
+ Location loc = mMockProviderLocation.get(getName());
+ if (loc == null) {
+ return false;
+ }
+ l.set(loc);
+ return true;
+ }
+ }
+
+ @Override
+ public int getStatus(Bundle extras) {
+ // We shouldn't normally need to lock, since this should only be called
+ // by the service with the lock held, but let's be paranid.
+ synchronized (mLocationListeners) {
+ String name = getName();
+ Integer s = mMockProviderStatus.get(name);
+ int status = (s == null) ? AVAILABLE : s.intValue();
+ Bundle newExtras = mMockProviderStatusExtras.get(name);
+ if (newExtras != null) {
+ extras.clear();
+ extras.putAll(newExtras);
+ }
+ return status;
+ }
+ }
+
+ @Override
+ public boolean isEnabled() {
+ // We shouldn't normally need to lock, since this should only be called
+ // by the service with the lock held, but let's be paranid.
+ synchronized (mLocationListeners) {
+ return mEnabledProviders.contains(getName());
+ }
+ }
+
+ @Override
+ public int getAccuracy() {
+ return mAccuracy;
+ }
+
+ @Override
+ public int getPowerRequirement() {
+ return mPowerRequirement;
+ }
+
+ @Override
+ public boolean hasMonetaryCost() {
+ return mHasMonetaryCost;
+ }
+
+ @Override
+ public boolean requiresCell() {
+ return mRequiresCell;
+ }
+
+ @Override
+ public boolean requiresNetwork() {
+ return mRequiresNetwork;
+ }
+
+ @Override
+ public boolean requiresSatellite() {
+ return mRequiresSatellite;
+ }
+
+ @Override
+ public boolean supportsAltitude() {
+ return mSupportsAltitude;
+ }
+
+ @Override
+ public boolean supportsBearing() {
+ return mSupportsBearing;
+ }
+
+ @Override
+ public boolean supportsSpeed() {
+ return mSupportsSpeed;
+ }
+ }
+
+ private void checkMockPermissionsSafe() {
+ boolean allowMocks = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.ALLOW_MOCK_LOCATION, 0) == 1;
+ if (!allowMocks) {
+ throw new SecurityException("Requires ACCESS_MOCK_LOCATION secure setting");
+ }
+
+ if (mContext.checkCallingPermission(ACCESS_MOCK_LOCATION) !=
+ PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires ACCESS_MOCK_LOCATION permission");
+ }
+ }
+
+ public void addTestProvider(String name, boolean requiresNetwork, boolean requiresSatellite,
+ boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude,
+ boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy) {
+ checkMockPermissionsSafe();
+
+ synchronized (mLocationListeners) {
+ MockProvider provider = new MockProvider(name, requiresNetwork, requiresSatellite,
+ requiresCell, hasMonetaryCost, supportsAltitude,
+ supportsSpeed, supportsBearing, powerRequirement, accuracy);
+ if (LocationProviderImpl.getProvider(name) != null) {
+ throw new IllegalArgumentException("Provider \"" + name + "\" already exists");
+ }
+ LocationProviderImpl.addProvider(provider);
+ updateProvidersLocked();
+ }
+ }
+
+ public void removeTestProvider(String provider) {
+ checkMockPermissionsSafe();
+ synchronized (mLocationListeners) {
+ LocationProviderImpl p = LocationProviderImpl.getProvider(provider);
+ if (p == null) {
+ throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
+ }
+ LocationProviderImpl.removeProvider(p);
+ updateProvidersLocked();
+ }
+ }
+
+ public void setTestProviderLocation(String provider, Location loc) {
+ checkMockPermissionsSafe();
+ synchronized (mLocationListeners) {
+ if (LocationProviderImpl.getProvider(provider) == null) {
+ throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
+ }
+ mMockProviderLocation.put(provider, loc);
+ }
+ }
+
+ public void clearTestProviderLocation(String provider) {
+ checkMockPermissionsSafe();
+ synchronized (mLocationListeners) {
+ if (LocationProviderImpl.getProvider(provider) == null) {
+ throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
+ }
+ mMockProviderLocation.remove(provider);
+ }
+ }
+
+ public void setTestProviderEnabled(String provider, boolean enabled) {
+ checkMockPermissionsSafe();
+ synchronized (mLocationListeners) {
+ if (LocationProviderImpl.getProvider(provider) == null) {
+ throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
+ }
+ if (enabled) {
+ mEnabledProviders.add(provider);
+ mDisabledProviders.remove(provider);
+ } else {
+ mEnabledProviders.remove(provider);
+ mDisabledProviders.add(provider);
+ }
+ updateProvidersLocked();
+ }
+ }
+
+ public void clearTestProviderEnabled(String provider) {
+ checkMockPermissionsSafe();
+ synchronized (mLocationListeners) {
+ if (LocationProviderImpl.getProvider(provider) == null) {
+ throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
+ }
+ mEnabledProviders.remove(provider);
+ mDisabledProviders.remove(provider);
+ updateProvidersLocked();
+ }
+ }
+
+ public void setTestProviderStatus(String provider, int status, Bundle extras, long updateTime) {
+ checkMockPermissionsSafe();
+ synchronized (mLocationListeners) {
+ if (LocationProviderImpl.getProvider(provider) == null) {
+ throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
+ }
+ mMockProviderStatus.put(provider, new Integer(status));
+ mMockProviderStatusExtras.put(provider, extras);
+ mMockProviderStatusUpdateTime.put(provider, new Long(updateTime));
+ }
+ }
+
+ public void clearTestProviderStatus(String provider) {
+ checkMockPermissionsSafe();
+ synchronized (mLocationListeners) {
+ if (LocationProviderImpl.getProvider(provider) == null) {
+ throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
+ }
+ mMockProviderStatus.remove(provider);
+ mMockProviderStatusExtras.remove(provider);
+ mMockProviderStatusUpdateTime.remove(provider);
+ }
+ }
+
+ private void log(String log) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, log);
+ }
+ }
+
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump AlarmManager from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ synchronized (mLocationListeners) {
+ pw.println("Current Location Manager state:");
+ pw.println(" sProvidersLoaded=" + sProvidersLoaded);
+ pw.println(" mGpsLocationProvider=" + mGpsLocationProvider);
+ pw.println(" mGpsNavigating=" + mGpsNavigating);
+ pw.println(" mNetworkLocationProvider=" + mNetworkLocationProvider);
+ pw.println(" mNetworkLocationInterface=" + mNetworkLocationInterface);
+ pw.println(" mLastSignalStrength=" + mLastSignalStrength
+ + " mLastRadioType=" + mLastRadioType);
+ pw.println(" mCellLocationUpdater=" + mCellLocationUpdater);
+ pw.println(" mLastCellState=" + mLastCellState);
+ pw.println(" mCollector=" + mCollector);
+ pw.println(" mAlarmInterval=" + mAlarmInterval
+ + " mScreenOn=" + mScreenOn
+ + " mWakeLockAcquireTime=" + mWakeLockAcquireTime);
+ pw.println(" mWakeLockGpsReceived=" + mWakeLockGpsReceived
+ + " mWakeLockNetworkReceived=" + mWakeLockNetworkReceived);
+ pw.println(" mWifiWakeLockAcquired=" + mWifiWakeLockAcquired
+ + " mCellWakeLockAcquired=" + mCellWakeLockAcquired);
+ pw.println(" Listeners:");
+ int N = mListeners.size();
+ for (int i=0; i<N; i++) {
+ pw.println(" " + mListeners.get(i));
+ }
+ pw.println(" Location Listeners:");
+ for (Map.Entry<Receiver, HashMap<String,UpdateRecord>> i
+ : mLocationListeners.entrySet()) {
+ pw.println(" " + i.getKey() + ":");
+ for (Map.Entry<String,UpdateRecord> j : i.getValue().entrySet()) {
+ pw.println(" " + j.getKey() + ":");
+ j.getValue().dump(pw, " ");
+ }
+ }
+ pw.println(" Last Fix Broadcasts:");
+ for (Map.Entry<Receiver, HashMap<String,Location>> i
+ : mLastFixBroadcast.entrySet()) {
+ pw.println(" " + i.getKey() + ":");
+ for (Map.Entry<String,Location> j : i.getValue().entrySet()) {
+ pw.println(" " + j.getKey() + ":");
+ j.getValue().dump(new PrintWriterPrinter(pw), " ");
+ }
+ }
+ pw.println(" Last Status Broadcasts:");
+ for (Map.Entry<Receiver, HashMap<String,Long>> i
+ : mLastStatusBroadcast.entrySet()) {
+ pw.println(" " + i.getKey() + ":");
+ for (Map.Entry<String,Long> j : i.getValue().entrySet()) {
+ pw.println(" " + j.getKey() + " -> 0x"
+ + Long.toHexString(j.getValue()));
+ }
+ }
+ pw.println(" Records by Provider:");
+ for (Map.Entry<String, ArrayList<UpdateRecord>> i
+ : mRecordsByProvider.entrySet()) {
+ pw.println(" " + i.getKey() + ":");
+ for (UpdateRecord j : i.getValue()) {
+ pw.println(" " + j + ":");
+ j.dump(pw, " ");
+ }
+ }
+ pw.println(" Locations by Provider:");
+ for (Map.Entry<String, Location> i
+ : mLocationsByProvider.entrySet()) {
+ pw.println(" " + i.getKey() + ":");
+ i.getValue().dump(new PrintWriterPrinter(pw), " ");
+ }
+ pw.println(" Last Known Locations:");
+ for (Map.Entry<String, Location> i
+ : mLastKnownLocation.entrySet()) {
+ pw.println(" " + i.getKey() + ":");
+ i.getValue().dump(new PrintWriterPrinter(pw), " ");
+ }
+ if (mProximityAlerts.size() > 0) {
+ pw.println(" Proximity Alerts:");
+ for (Map.Entry<PendingIntent, ProximityAlert> i
+ : mProximityAlerts.entrySet()) {
+ pw.println(" " + i.getKey() + ":");
+ i.getValue().dump(pw, " ");
+ }
+ }
+ if (mProximitiesEntered.size() > 0) {
+ pw.println(" Proximities Entered:");
+ for (ProximityAlert i : mProximitiesEntered) {
+ pw.println(" " + i + ":");
+ i.dump(pw, " ");
+ }
+ }
+ pw.println(" mProximityListener=" + mProximityListener);
+ if (mEnabledProviders.size() > 0) {
+ pw.println(" Enabled Providers:");
+ for (String i : mEnabledProviders) {
+ pw.println(" " + i);
+ }
+
+ }
+ if (mDisabledProviders.size() > 0) {
+ pw.println(" Disabled Providers:");
+ for (String i : mDisabledProviders) {
+ pw.println(" " + i);
+ }
+
+ }
+ if (mMockProviders.size() > 0) {
+ pw.println(" Mock Providers:");
+ for (Map.Entry<String, MockProvider> i : mMockProviders.entrySet()) {
+ pw.println(" " + i.getKey() + " -> " + i.getValue());
+ }
+ }
+ if (mMockProviderLocation.size() > 0) {
+ pw.println(" Mock Provider Location:");
+ for (Map.Entry<String, Location> i : mMockProviderLocation.entrySet()) {
+ pw.println(" " + i.getKey() + ":");
+ i.getValue().dump(new PrintWriterPrinter(pw), " ");
+ }
+ }
+ if (mMockProviderStatus.size() > 0) {
+ pw.println(" Mock Provider Status:");
+ for (Map.Entry<String, Integer> i : mMockProviderStatus.entrySet()) {
+ pw.println(" " + i.getKey() + " -> 0x"
+ + Integer.toHexString(i.getValue()));
+ }
+ }
+ if (mMockProviderStatusExtras.size() > 0) {
+ pw.println(" Mock Provider Status Extras:");
+ for (Map.Entry<String, Bundle> i : mMockProviderStatusExtras.entrySet()) {
+ pw.println(" " + i.getKey() + " -> " + i.getValue());
+ }
+ }
+ if (mMockProviderStatusUpdateTime.size() > 0) {
+ pw.println(" Mock Provider Status Update Time:");
+ for (Map.Entry<String, Long> i : mMockProviderStatusUpdateTime.entrySet()) {
+ pw.println(" " + i.getKey() + " -> " + i.getValue());
+ }
+ }
+ pw.println(" Reported GPS UIDs @ seq " + mReportedGpsSeq + ":");
+ N = mReportedGpsUids.size();
+ for (int i=0; i<N; i++) {
+ pw.println(" UID " + mReportedGpsUids.keyAt(i)
+ + " seq=" + mReportedGpsUids.valueAt(i));
+ }
+ }
+ }
+}
+